@import BookData._ @p To get started with Scala.js, you will need to prepare a few things: @ul @li @lnk("sbt", "http://www.scala-sbt.org/"): SBT is the most common build-tool in the Scala community, and is what we will use for building our Scala.js application. Their home page has a link to download and install it. (If you are already using Typesafe Activator, that is effectively sbt.) @li An editor for Scala: @lnk("IntelliJ Scala", "http://blog.jetbrains.com/scala/") and @lnk("Eclipse ScalaIDE", "http://scala-ide.org/") are the most popular choices and work on all platforms, though there are others. @li @lnk("Git", "http://git-scm.com/"): This is a version-control system that we will use to download and manage our Scala.js projects. @li A terminal: on OSX you have @lnk("Terminal.app", "http://guides.macrumors.com/Terminal") already installed, in Linux you have @lnk("Terminal", "https://help.ubuntu.com/community/UsingTheTerminal"), and on Windows you have @lnk("PowerShell", "http://en.wikipedia.org/wiki/Windows_PowerShell"). @li Your favorite web browser: @lnk("Chrome", "https://www.google.com/chrome") and @lnk("Firefox", "https://www.mozilla.org/en-US/firefox") are the most popular. @p If you've worked with Scala before, you probably already have most of these installed. Otherwise, take a moment to download them before we get to work. @p The quickest way to get started with Scala.js is to @code{git clone} @lnk("workbench-example-app", "https://github.com/lihaoyi/workbench-example-app"), go into the repository root, and run @code{sbt ~fastOptJS} @hl.bash git clone https://github.com/lihaoyi/workbench-example-app cd workbench-example-app sbt ~fastOptJS @p This should result in a bunch of spam to the console, and may take a few minutes the first time as SBT resolves and downloads all necessary dependencies. A successful run looks like this @pre haoyi-mbp:Workspace haoyi$ git clone https://github.com/lihaoyi/workbench-example-app Cloning into 'workbench-example-app'... remote: Counting objects: 876, done. remote: Total 876 (delta 0), reused 0 (delta 0) Receiving objects: 100% (876/876), 676.59 KiB | 317.00 KiB/s, done. Resolving deltas: 100% (308/308), done. Checking connectivity... done. haoyi-mbp:Workspace haoyi$ cd workbench-example-app/ haoyi-mbp:workbench-example-app haoyi$ sbt ~fastOptJS [info] Loading global plugins from /Users/haoyi/.sbt/0.13/plugins [info] Updating {file:/Users/haoyi/.sbt/0.13/plugins/}global-plugins... [info] Resolving org.fusesource.jansi#jansi;1.4 ... [info] Done updating. [info] Loading project definition from /Users/haoyi/Dropbox (Personal)/Workspace/workbench-example-app/project [info] Updating {file:/Users/haoyi/Dropbox%20(Personal)/Workspace/workbench-example-app/project/}workbench-example-app-build... [info] Resolving org.fusesource.jansi#jansi;1.4 ... [info] Done updating. [info] Set current project to Example (in build file:/Users/haoyi/Dropbox%20(Personal)/Workspace/workbench-example-app/) [INFO] [10/26/2014 15:42:09.791] [SystemLol-akka.actor.default-dispatcher-2] [akka://SystemLol/user/IO-HTTP/listener-0] Bound to localhost/127.0.0.1:12345 [info] Updating {file:/Users/haoyi/Dropbox%20(Personal)/Workspace/workbench-example-app/}workbench-example-app... [info] Resolving jline#jline;2.12 ... [info] Done updating. [info] Compiling 1 Scala source to /Users/haoyi/Dropbox (Personal)/Workspace/workbench-example-app/target/scala-2.11/classes... [info] Fast optimizing /Users/haoyi/Dropbox (Personal)/Workspace/workbench-example-app/target/scala-2.11/example-fastopt.js [info] workbench: Checking example-fastopt.js [info] workbench: Refreshing http://localhost:12345/target/scala-2.11/example-fastopt.js [success] Total time: 11 s, completed Oct 26, 2014 3:42:21 PM 1. Waiting for source changes... (press enter to interrupt) @p The line @code{Waiting for source changes...} is telling you that your Scala.js program is ready! Now, when you go to the web URL @code{http://localhost:12345/target/scala-2.11/classes/index-dev.html} in your browser, you should see the following: @img(src:="images/Hello World.png", maxWidth:="100%") @p Congratulations, you just built and ran your first Scala.js application! If something here does not happen as expected, it means that one of the steps did not complete successfully. Make sure you can get this working before you proceed onward. @sect{Opening up the Project} @p The next thing to do once you have the project built and running in your browser is to load it into your editor. Both IntelliJ and Eclipse should let you import the Scala.js project without any hassle. Opening it and navigating to @code{ScalaJSExample.scala} would look like this: @img(src:="images/IntelliJ Hello.png", maxWidth:="100%") @p Let's try changing one line to change the background fill from black to white: @hl.diff - ctx.fillStyle = "black" + ctx.fillStyle = "white" @p Because we started @code{sbt ~fastOptJS} with the @code{~} prefix earlier, it should pick up the change and automatically recompile. The example project is set up to automatically refresh the page when recompilation is complete. @img(src:="images/Hello World White.png", maxWidth:="100%") @p If you open up your browser's developer console, you'll see that the SBT log output is being mirrored there: @img(src:="images/Hello World Console.png", maxWidth:="100%") @p Apart from the SBT log output (which is handled by Workbench) any @hl.scala{println}s in your Scala.js code will also end up in the browser console (the @code{main} you see in the console is printed inside the Scala.js application, see if you can find it!) and so will the stack traces for any thrown exceptions. @sect{The Application Code} @p We've downloaded, compiled, ran, and made changes to our first Scala.js application. Let's now take a closer look at the code that we just ran: @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala") @p It's a good chunk of code, though not a huge amount. To someone who didn't know about Scala.js, they would just think it's normal Scala, albeit with this unusual @hl.scala{dom} library and a few weird annotations. Let's pick it apart starting from the top: @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "case class Point", "@JSExport") @p Here we are defining a @hl.scala{Point} case class which represents a X/Y position, with some basic operators defined on it. This is done mostly for convenience later on, when we want to manipulate these two-dimensional points. Scala.js is Scala, and supports the entirety of the Scala language. @hl.scala{Point} here behaves identically as it would if you had run Scala on the JVM. @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "@JSExport", "val ctx") @p This @hl.scala("@JSExport") annotation is used to tell Scala.js that you want this method to be visible and callable from Javascript. By default, Scala.js does @sect.ref("Fast Optimization", "dead code elimination") and removes any methods or classes which are not used. This is done to keep the compiled executables a reasonable size, since most projects use only a small fraction of e.g. the standard library. @hl.scala("@JSExport") is used to tell Scala.js that the @hl.scala{ScalaJSExample} object and its @hl.scala{def main} method are entry points to the program. Even if they aren't called anywhere internally, they are called externally by Javascript that the Scala.js compiler is not aware of, and should not be removed. In this case, we are going to call this method from Javascript to start the Scala.js program. @p Apart from this annotation, @hl.scala{ScalaJSExample} is just a normal Scala @hl.scala{object}, and behaves like one in every way. Note that the main-method in this case takes a @lnk.dom.HTMLCanvasElement: your exported methods can have any signature, with arbitrary arity or types for parameters or the return value. This is in contrast to the main method on the JVM which always takes an @hl.scala{Array[String]} and returns @hl.scala{Unit}. In fact, there's nothing special about this method at all! It's like any other exported method, we just happen to attribute it the "main" entry point. It is entirely possible to define multiple exported classes and methods, and build a "library" using Scala.js of methods that are intended for external Javascript to use. @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "val ctx", "var count") @p Here we are retrieving a handle to the canvas we will draw on using @hl.scala{document.getElementById}, and from it we can get a @lnk.dom.CanvasRenderingContext2D which we actually use to draw on it. @p We need to perform the @hl.scala{asInstanceOf} call because depending on what you pass to @hl.scala{getElementById} and @hl.scala{getContext}, you could be returned elements and contexts of different types. Hence we need to tell the compiler explicitly that we're expecting a @lnk.dom.HTMLCanvasElement and @lnk.dom.CanvasRenderingContext2D back from these methods for the strings we passed in. @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "def run", "dom.setInterval") @p This is the part of the Scala.js program which does the real work. It runs 10 iterations of a @lnk("small algorithm", "http://en.wikipedia.org/wiki/Sierpinski_triangle#Chaos_game") that generates a Sierpinski Triangle point-by-point. The steps, as described by the linked article, are roughly: @ul @li Pick a random corner of the large-triangle @li Move your current-position @hl.scala{p} halfway between its current location and that corner @li Draw a dot @li Repeat @p In this example, the triangle is hard-coded to be 255 pixels high by 255 pixels wide, and some math is done to pick a color for each dot which will give the triangle a pretty gradient. @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "dom.setInterval") @p Now this is the call that actually does the useful work. All this method does is call @hl.scala{dom.setInterval}, which tells the browser to run the @hl.scala{run} method every 50 milliseconds. As mentioned earlier, the @hl.scala{dom.*} methods are simply facades to their native Javascript equivalents, and @hl.scala{dom.setInterval} is @lnk("no different", "https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers.setInterval"). Note how you can pass a Scala lambda to @hl.scala{setInterval} to have it called by the browser, where in Javascript you'd need to pass a Javascript @hl.javascript{function(){...}} @sect{The Project Code} @p We've already taken a look at the application code for a simple, self-contained Scala.js application, but this application is not @i{entirely} self contained. It's wrapped in a small SBT project that sets up the necessary dependencies and infrastructure for this application to work. @sect{project/build.sbt} @hl.ref(cloneRoot + "workbench-example-app/project/build.sbt") @p This is the list of SBT plugins used by this small example application. There are two of them: the Scala.js plugin (which contains the Scala.js compiler and other things, e.g. tasks such as @code{fastOptJS}) and the @lnk("Workbench", "https://github.com/lihaoyi/workbench") plugin, which is used to provide the auto-reload-on-change behavior and the forwarding of SBT logspam to the browser console. @p Of the two, only the Scala.js plugin is really necessary. The Workbench plugin is a convenience that makes development easier. Without it you'd need to keep a terminal open to view the SBT logspam, and manually refresh the page when compilation finished. Not the end of the world. @sect{build.sbt} @hl.ref(cloneRoot + "workbench-example-app/build.sbt") @p The @code{build.sbt} project file for this application is similarly unremarkable: It includes the settings for the two SBT plugins we saw earlier, as well as boilerplate @hl.scala{name}/@hl.scala{version}/@hl.scala{scalaVersion} values common to all projects. @p Of interest is the @hl.scala{libraryDependencies}. In Scala-JVM, this key is used to declare dependencies on libraries from Maven Central, so you can use them in your Scala-JVM projects. In Scala.js, the same key is used to declare dependencies on libraries so you can use them in your Scala.js projects! Re-usable libraries can be built and published with Scala.js just as you do on Scala-JVM, and here we make use of one which provides the typed facades with which we used to access the DOM in the application code. @p Lastly, we have two Workbench related settings: @hl.scala{bootSnippet} basically tells Workbench how to restart your application when a new compilation run finishes, and @hl.scala{updateBrowsers} actually tells it to perform this application-restarting. @sect{src/main/resources/index-dev.html} @hl.ref(cloneRoot + "workbench-example-app/src/main/resources/index-dev.html") @p This is the HTML page which our toy app lives in, and the same page that we have so far been using to view the app in the browser. To anyone who has used HTML, most of it is probably familiar. Things of note are the @hl.html{