summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlihaoyi <haoyi.sg@gmail.com>2014-11-23 19:42:32 -0800
committerlihaoyi <haoyi.sg@gmail.com>2014-11-23 19:42:32 -0800
commit0ca754864c76f546be651d1e4279d5b73883300c (patch)
treef6765858d5cfc622d87c77d21defe6d97fea7e24
parent0645ba4ec953d2f988810d9d07fc7ab0594e03ce (diff)
downloadhands-on-scala-js-0ca754864c76f546be651d1e4279d5b73883300c.tar.gz
hands-on-scala-js-0ca754864c76f546be651d1e4279d5b73883300c.tar.bz2
hands-on-scala-js-0ca754864c76f546be651d1e4279d5b73883300c.zip
First read-through
-rw-r--r--book/src/main/scala/book/Utils.scala8
-rw-r--r--book/src/main/scalatex/book/Index.scalatex7
-rw-r--r--book/src/main/scalatex/book/Intro.scalatex10
-rw-r--r--book/src/main/scalatex/book/handson/CanvasApp.scalatex6
-rw-r--r--book/src/main/scalatex/book/handson/ClientServer.scalatex13
-rw-r--r--book/src/main/scalatex/book/handson/CommandLine.scalatex16
-rw-r--r--book/src/main/scalatex/book/handson/GettingStarted.scalatex10
-rw-r--r--book/src/main/scalatex/book/handson/PublishingModules.scalatex6
-rw-r--r--book/src/main/scalatex/book/handson/WebPage.scalatex21
-rw-r--r--book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex2
-rw-r--r--book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex86
-rw-r--r--book/src/main/scalatex/book/indepth/DesignSpace.scalatex53
-rw-r--r--book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex47
-rw-r--r--examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala2
-rw-r--r--examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala1
-rw-r--r--examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala1
-rw-r--r--examples/demos/src/main/scala/canvasapp/FlappyLine.scala2
17 files changed, 146 insertions, 145 deletions
diff --git a/book/src/main/scala/book/Utils.scala b/book/src/main/scala/book/Utils.scala
index 171d0ad..91d76a7 100644
--- a/book/src/main/scala/book/Utils.scala
+++ b/book/src/main/scala/book/Utils.scala
@@ -109,7 +109,7 @@ object lnk{
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 Nodejs = lnk("Node.js", "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/")
@@ -118,7 +118,7 @@ object lnk{
}
object github{
val Scalatags = lnk("Scalatags", "https://github.com/lihaoyi/scalatags")
- val uPickle= lnk("Scalatags", "https://github.com/lihaoyi/upickle")
+ val uPickle= lnk("uPickle", "https://github.com/lihaoyi/upickle")
val scalaPickling = lnk("scala-pickling", "https://github.com/scala/pickling")
}
}
@@ -137,7 +137,7 @@ object hl{
.dropWhile(_ == "")
.mkString("\n")
- pre(code(cls:=lang + " highlight-me", stripped))
+ pre(code(cls:=lang + " highlight-me hljs", stripped))
}
}
@@ -181,7 +181,7 @@ object hl{
pre(
- code(cls:=lang + " highlight-me", blob),
+ code(cls:=lang + " highlight-me hljs", blob),
a(
cls:="header-link",
i(cls:="fa fa-link "),
diff --git a/book/src/main/scalatex/book/Index.scalatex b/book/src/main/scalatex/book/Index.scalatex
index 1e6600f..7a7e2b8 100644
--- a/book/src/main/scalatex/book/Index.scalatex
+++ b/book/src/main/scalatex/book/Index.scalatex
@@ -18,13 +18,13 @@ is a set of detailed expositions on various parts of the Scala.js platform. Noth
@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. This book is an introduction to Scala.js, which aims to get you from knowing-nothing about it to being relatively proficient.
@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 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.
+ 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. Feel free to explore the navigation bar on the left to find chapters of interest.
@p
Even if we do not require any familiarity of Scala.js, this book nonetheless assumes a good amount of background knowledge: of Scala, of Javascript, and of web development as a whole. In general, you will not need deep knowledge of any of these subjects, though if you are coming in entirely without knowledge of any one of them, you'll have to be willing to spend time Google-ing things and picking things up as we go along. Someone who comes in without previous web-dev experience may miss or not-notice many of the nice touches and benefits that Scala.js brings to the table, having never done web-dev any other way,
@p
- Many of the code samples are taken from examples available on the book's @lnk("Github Page", "https://github.com/lihaoyi/scala-js-book"); for those code samples (e.g. the animation above), there is a link in the bottom-right corner of the snippet that you can click on to go to the original code. These come in handy if you find you need additional context around the snippet, e.g. what imports you need for the code to work, or what the complete executable example looks like.
+ Many of the code samples are taken from examples available on the book's @lnk("Github Page", "https://github.com/lihaoyi/scala-js-book"); for those code samples (e.g. the animation above), there is a @i(cls:="fa fa-link ") link in the bottom-right corner of the snippet that you can click on to go to the original code. These come in handy if you find you need additional context around the snippet, e.g. what imports you need for the code to work, or what the complete executable example looks like.
@p
This book is roughly divided into two sections:
@@ -70,9 +70,6 @@ is a set of detailed expositions on various parts of the Scala.js platform. Noth
@sect{Advanced Techniques}
@indepth.AdvancedTechniques()
- @sect{Javascript Interoperability}
- @indepth.JavascriptInterop()
-
@sect{Deviations from Scala-JVM}
@indepth.SemanticDifferences()
diff --git a/book/src/main/scalatex/book/Intro.scalatex b/book/src/main/scalatex/book/Intro.scalatex
index 8c51032..10a2638 100644
--- a/book/src/main/scalatex/book/Intro.scalatex
+++ b/book/src/main/scalatex/book/Intro.scalatex
@@ -9,7 +9,8 @@
def main() = {
var x = 0
while(x < 10) x += 3
- println(x) // 12
+ println(x)
+ // 12
}
}
@@ -20,7 +21,8 @@
while ((x < 10)) {
x = ((x + 3) | 0)
};
- ScalaJS.m.s_Predef().println__O__V(x) // 12
+ ScalaJS.m.s_Predef().println__O__V(x)
+ // 12
});
@p
@@ -60,7 +62,7 @@
In a large code-base, finding out what methods or properties a variable has is often a long chase through dozens of files to see how it ended up being passed to the current function. Refactorings, which are OK when you can just test the code see if it works, become dangerous when your code base is large enough that "just test all the code" would take hours. Language-warts which are slightly annoying in small programs become a minefield in large ones: it's only a matter of time before you hit one, often in code you did-not/cannot test, resulting in breakages in production.
@p
- Apart from the inherent danger of the language, Javascript has another major problem: the language has left so many things unspecified, yet at the same time provides the ability to emulate these things in a variety of ways. This means that rather than having a single way of e.g. defining a class and instantiating an object, there is a decade-long debate between a dozen different and equally-bad, hand-crafted alternatives. Large code-bases use third-party libraries, and most are guaranteed (purely due to how stastistics work) to do these basic things differently from your own code, making understanding these disparate code-bases (e.g. when something goes wrong) very difficult.
+ Apart from the inherent danger of the language, Javascript has another major problem: the language has left many things unspecified, yet at the same time provides the ability to emulate these things in a variety of ways. This means that rather than having a single way of e.g. defining a class and instantiating an object, there is a decade-long debate between a dozen different and equally-bad, hand-crafted alternatives. Large code-bases use third-party libraries, and most are guaranteed (purely due to how stastistics work) to do these basic things differently from your own code, making understanding these disparate code-bases (e.g. when something goes wrong) very difficult.
@p
To work in Javascript, you need the discipline to limit yourself to the sane subset of the language, avoiding all the pitfalls along the way:
@@ -118,7 +120,7 @@
@p
- Not only do you have an expressive language with static types, you also have great tooling with IDEs like IntelliJ and Eclipse, a rich library of standard collections, and many other modern conveniences that we take for granted but are curiously missing when working in the wild west of web development: the browser! You get all of the upside of developing for the web platform,
+ Not only do you have an expressive language with static types, you also have great tooling with IDEs like IntelliJ and Eclipse, a rich library of standard collections, and many other modern conveniences that we take for granted but are curiously missing when working in the wild west of web development: the browser! You get all of the upside of developing for the web platform.
@p
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.
diff --git a/book/src/main/scalatex/book/handson/CanvasApp.scalatex b/book/src/main/scalatex/book/handson/CanvasApp.scalatex
index 41be815..e68adb7 100644
--- a/book/src/main/scalatex/book/handson/CanvasApp.scalatex
+++ b/book/src/main/scalatex/book/handson/CanvasApp.scalatex
@@ -45,7 +45,7 @@
@img(src:="images/Dropdown.png", maxWidth:="100%")
@p
- Apart from mouse events, keyboard events, scroll events, input events, etc. are all usable from Scala.js as you'd expect
+ Apart from mouse events, keyboard events, scroll events, input events, etc. are all usable from Scala.js as you'd expect. If you have problems getting this to work, feel free to click on the link @i(cls:="fa fa-link ") icon below the code snippet to see what the full code for the example looks like
@sect{Making a Clock using setInterval}
@@ -71,7 +71,7 @@
@BookData.example(canvas, "Clock().main")
@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 full 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}. Again, click on the link @i(cls:="fa fa-link ") icon to view the full-code if you're having trouble here.
@sect{Tying it together: Flappy Box}
@@ -103,7 +103,7 @@
This section of the code is peripherally necessary, but not core to the implementation or logic of Flappy Box. We see the same @hl.scala{canvas}/@hl.scala{renderer} logic we've seen in all our examples, along with some logic to make the canvas a reasonable size, and some configuration of how we will render text to the canvas.
@p
- In general, code like this will usually end up being necessary in a Scala.js program: the Javascript APIs that the browser provides to do things often ends up being somewhat roundabout and verbose. It's somewhat annoying to have to do for a small program such as this one, but in a larger application, the cost is both spread out over thousands of lines of code and also typically hidden away in some helpers, so you don't have to mess with this stuff unless you really want to.
+ In general, code like this will usually end up being necessary in a Scala.js program: the Javascript APIs that the browser provides to do things often ends up being somewhat roundabout and verbose. It's somewhat annoying to have to do for a small program such as this one, but in a larger application, the cost is both spread out over thousands of lines of code and also typically hidden away in helper functions, so the verbosity and non-idiomatic-scala-ness doesn't bother you much.
@sect{Defining our State}
@hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "/*variables*/", end="def runLive")
diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex
index ca8db07..0a41dae 100644
--- a/book/src/main/scalatex/book/handson/ClientServer.scalatex
+++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex
@@ -40,7 +40,7 @@
@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.
+ 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 additional 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:
@@ -110,15 +110,15 @@
@hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "")
@p
- Three times on the server and once on the client! What's worse, two of the appearances of @i{list} are in string literals, which are not checked by the compiler to match up with themselves or the name of the method @hl.scala{list}. Apart from this, there is one other piece of duplication that is unchecked: the type being returned from @hl.scala{list} (@hl.scala{Seq[FileData]} is being repeated on the client in @hl.scala{upickle.read[Seq[FileData]]} in order to de-serialize the serialized data. This leaves three wide-open opportunities for error:
+ Three times on the server and once on the client! What's worse, two of the appearances of @hl.scala{"list"} are in string literals, which are not checked by the compiler to match up with themselves or the name of the method @hl.scala{list}. Apart from this, there is one other piece of duplication that is unchecked: the type being returned from @hl.scala{list} (@hl.scala{Seq[FileData]}) is being repeated on the client in @hl.scala{upickle.read[Seq[FileData]]} in order to de-serialize the serialized data. This leaves three opportunities for error wide-open:
@ul
@li
- You could change the string literals "list" and forget to change the method-name @hl.scala{list}, thus confusing future maintainers of the code.
+ You could change the string literals @hl.scala{"list"} and forget to change the method-name @hl.scala{list}, thus confusing future maintainers of the code.
@li
- You could change one of literal "list"s but forget to change the other, thus causing an error at run-time (e.g. a 404 NOT FOUND response)
+ You could change one of literal @hl.scala{"list"}s but forget to change the other, thus causing an error at run-time (e.g. a 404 NOT FOUND response)
@li
- You could update the return type of @hl.scala{list} and forget to update the deserialization call on the client, resulting in a deserialization failure at runtime.
+ You could update the return type of the @hl.scala{list} method and forget to update the @hl.scala{upickle.read} deserialization call on the client, resulting in a deserialization failure at runtime.
@p
@@ -130,11 +130,10 @@
@p
@lnk("Autowire", "https://github.com/lihaoyi/autowire") is a library that turns your request routing layer from a fragile, hand-crafted mess into a solid, type-checked, boilerplate-free experience. Autowire basically turns what was previously a stringly-typed, hand-crafted Ajax call and route:
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala", """path("ajax" / "list")""", "")
@hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "")
@p
- Into a single, type-checked function call:
+ Into a safe, type-checked function call:
@hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", ".call()", "")
diff --git a/book/src/main/scalatex/book/handson/CommandLine.scalatex b/book/src/main/scalatex/book/handson/CommandLine.scalatex
index 29bd77f..0663378 100644
--- a/book/src/main/scalatex/book/handson/CommandLine.scalatex
+++ b/book/src/main/scalatex/book/handson/CommandLine.scalatex
@@ -81,7 +81,7 @@
@hl.bash
> fastOptJS
@p
- @code{fastOptJS} is a command we've used in earlier chapters. It basically runs the Fast Optimization stage of the compilation pipeline. This results in a moderately-sized executable, which you can then load in the browser with a @hl.html{<script>} tag and run.
+ @code{fastOptJS} is a command we've used in earlier chapters. It basically runs the @sect.ref{Fast Optimization} stage of the compilation pipeline. This results in a moderately-sized executable, which you can then load in the browser with a @hl.html{<script>} tag and run.
@p
This is the first phase which actually results in an executable blob of Javascript. I won't go into much detail about this command: you've used it before, and more details about the particular kind of optimization and how it fits into the large process is available in the chapter on The Compilation Pipeline. Nonetheless, it's fast, produces not-too-huge output code, and is what you typically use for iterative development in the browser.
@@ -90,7 +90,7 @@
@hl.bash
> fullOptJS
@p
- @code{fullOptJS} is another command that we've seen before: it performs an aggressive, somewhat slower optimization pass on the generated Javascript. This results in a much smaller executable Javascript blob, which you can also load via a @hl.html{<script>} tag and run.
+ @code{fullOptJS} is another command that we've seen before: it performs an aggressive, somewhat slower @sect.ref{Full Optimization} pass on the generated Javascript. This results in a much smaller executable Javascript blob, which you can also load via a @hl.html{<script>} tag and run.
@p
Again, I won't go into much details, as exactly what this optimization does is described in the chapter on the Compilation Pipeline. This command is somewhat too-slow to be running during iterative development, and is instead typically used just before deployment to minimize the size of the file your users have to download.
@@ -117,7 +117,7 @@
In Scala.js, 1/0 is 0!
@p
- This exhibits the weirdness of integer divide-by-zero in Scala.js, which is one of the few ways in which Scala.js deviates from Scala-JVM. This also shows us we're really running on Scala.js: on Scala-JVM, integer divide-by-zero throws an exception rather than returning zero!
+ This exhibits the weirdness of integer divide-by-zero in Scala.js, which is one of the few ways in which @sect.ref("Deviations from Scala-JVM", "Scala.js deviates from Scala-JVM"). This also shows us we're really running on Scala.js: on Scala-JVM, integer divide-by-zero throws an exception rather than returning zero!
@p
One thing you may be wondering is: when you run a Scala.js program in the terminal, how does it execute the output Javascript? What about the DOM? and Ajax calls? Can it access the filesystem? The answer to all these questions is "it depends": it turns out there are multiple ways you can run Scala.js from the command-line:
@@ -131,7 +131,7 @@
@b{PhantomJS} using @code{sbt fastOptStage::run} or @code{sbt fullOptStage::run}, having installed PhantomJS separately, and turned on @hl.scala{requiresDOM := true} in SBT
@p
- Typically, the best way to get started is using Rhino and @code{sbt run}, since it's setup-free, and setting up Node.js or PhantomJS later as necessary. The next two sections elaborate on the differences between these ways of running your code. Check out the later sections on Headless Runtimes and Run Configurations to learn more about the other settings and why you would want to use them.
+ Typically, the best way to get started is using Rhino and @code{sbt run}, since it's setup-free, and setting up Node.js or PhantomJS later as necessary. The next two sections elaborate on the differences between these ways of running your code. Check out the later sections on @sect.ref{Headless Runtimes} and @sect.ref{Run Configurations} to learn more about the other settings and why you would want to use them.
@sect{The test Command}
@hl.bash
@@ -142,16 +142,16 @@
@p
The difference is that instead of simply running your @code{main} method, @code{sbt test} runs whatever test-suite you have set-up, which will look through your @code{tests/} folder to find suites of tests it has to run, and aggregate the results formatted nicely for you to see. The exact operation of this depends on which testing library you're using
@p
- We won't spend much time talking about @code{sbt test} here. Not because it's not important: it most certainly is! Rather, we will be spending a good amount of time setting up tests in the next chapter, so feel free to jump ahead if you want to see an example usage of @code{sbt test}.
+ We won't spend much time talking about @code{sbt test} here. Not because it's not important: it most certainly is! Rather, we will be spending a good amount of time setting up tests in @sect.ref("Cross Publishing Libraries", "the next chapter"), so feel free to jump ahead if you want to see an example usage of @code{sbt test}.
@sect{Headless Runtimes}
@ul
@li
- @b{Rhino} is the default way of running Scala.js applications, and occurs when you hit @code{sbt run} from the terminal. The upside of using @lnk.misc.Rhino is that it is pure-Java, and doesn't need any additional binaries or installation. The downside of using Rhino is that it is slow: maybe a hundred times slower than the alternatives, making it not suitable for running long-running, compute-intensive programs. Furthermore, it's a very sparse runtime environment, with no DOM access or similar.
+ @lnk.misc.Rhino is the default way of running Scala.js applications, and occurs when you hit @code{sbt run} from the terminal. The upside of using Rhino is that it is pure-Java, and doesn't need any additional binaries or installation. The downside of using Rhino is that it is slow: maybe a hundred times slower than the alternatives, making it not suitable for running long-running, compute-intensive programs. Furthermore, it's a very sparse runtime environment, with no DOM access or similar.
@li
- @b{Node.js}, a relatively new Javascript runtime based on Google's V8 Javascript engine, @lnk.misc.Nodejs lets you run your Scala.js application from the command line much faster than in Rhino, with performance that matches that of modern browsers. However, you need to separately @lnk("install Node.js", "http://nodejs.org/download/") in order to use it. Like Rhino, it comes with a bare-minimal runtime environment, with no DOM or browser-related functionality. You need to run @code{sbt fastOptStage::run} to run using Node.js.
+ @lnk.misc.Nodejs, a relatively new Javascript runtime based on Google's V8 Javascript engine, Node.js lets you run your Scala.js application from the command line much faster than in Rhino, with performance that matches that of modern browsers. However, you need to separately @lnk("install Node.js", "http://nodejs.org/download/") in order to use it. Like Rhino, it comes with a bare-minimal runtime environment, with no DOM or browser-related functionality. You need to run @code{sbt fastOptStage::run} to run using Node.js.
@li
- @b{PhantomJS} is a headless Webkit browser. This means that unlike Node.js or Rhino, @lnk.misc.PhantomJS provides you with a full DOM and all its APIs to use in your tests, if you wish to e.g. test interactions with the HTML of the web page. On the other hand, it is somewhat slower than Node.js, though still much faster than Rhino. Like Node.js, it needs to be installed separately. You need to run You need to run @code{sbt fastOptStage::run}, as well as setting the @hl.scala{requiresDOM := true} flag in your SBT configuration, to use PhantomJS.
+ @lnk.misc.PhantomJS is a headless Webkit browser. This means that unlike Node.js or Rhino, PhantomJS provides you with a full DOM and all its APIs to use in your tests, if you wish to e.g. test interactions with the HTML of the web page. On the other hand, it is somewhat slower than Node.js, though still much faster than Rhino. Like Node.js, it needs to be installed separately. You need to run You need to run @code{sbt fastOptStage::run}, as well as setting the @hl.scala{requiresDOM := true} flag in your SBT configuration, to use PhantomJS.
@p
These are your three options to run your Scala.js code via the command-line. Generally, it's easiest to get started with Rhino since it's the default and requires no setup, though you may find it worthwhile to setup Node or Phantom if you need additional speed or DOM-integration in your runs.
diff --git a/book/src/main/scalatex/book/handson/GettingStarted.scalatex b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
index 314a425..c31a14b 100644
--- a/book/src/main/scalatex/book/handson/GettingStarted.scalatex
+++ b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
@@ -111,10 +111,10 @@
@hl.ref("output/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 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.
+ 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.
+ 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("output/workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "val ctx", "var count")
@@ -181,7 +181,7 @@
@hl.ref("output/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 Script tags: @hl.scala{"../example-fastopt.js"} Is the executable blob spat out by the compiler, which we need to include in the HTML page for anything to happen. This is where the results of your compiled Scala code appear. @hl.scala{"workbench.js"} is the client for the Workbench plugin that connects to SBT, reloads the browser and forwards logspam to the browser console.
+ 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{<script>} tags: @hl.scala{"../example-fastopt.js"} Is the executable blob spat out by the compiler, which we need to include in the HTML page for anything to happen. This is where the results of your compiled Scala code appear. @hl.scala{"workbench.js"} is the client for the Workbench plugin that connects to SBT, reloads the browser and forwards logspam to the browser console.
@p
The @hl.scala{ScalaJSExample().main()} call is what kicks off the Scala.js application and starts its execution. Scala.js follows Scala semantics in that @hl.scala{object}s are evaluated lazily, with no top-level code allowed. This is in contrast to Javascript, where you can include top-level statements and object-literals in your code which execute immediately. In Scala.js, nothing happens when @code{../example-fastopt.js} is imported! We have to call the main-method first. In this case, we're passing the canvas object (attained using @hl.javascript{getElementById}) to it so it knows where to do its thing.
@@ -241,11 +241,11 @@
var g = ((((255 - ScalaJS.m.Lexample_ScalaJSExample().p$1.x$1) | 0) * height) | 0);
@p
- As you can see, this code is still very verbose, with lots of unnecessarily long identifiers such as @hl.javascript{Lexample_ScalaJSExample} in it. This is because we've only performed the @i{fast optimization} on this file, to try and keep the time taken to edit -> compile while developing reasonably short.
+ As you can see, this code is still very verbose, with lots of unnecessarily long identifiers such as @hl.javascript{Lexample_ScalaJSExample} in it. This is because we've only performed the @sect.ref{Fast Optimization} on this file, to try and keep the time taken to edit -> compile while developing reasonably short.
@sect{Optimization}
@p
- If we're planning on publishing the app for real, we can run the @i{full optimization}. This takes several seconds longer than the @i{fast optimization}, but results in a significantly smaller and leaner output file @code{example-opt.js}.
+ If we're planning on publishing the app for real, we can run the @sect.ref{Full Optimization}. This takes several seconds longer than the @sect.ref{Fast Optimization}, but results in a significantly smaller and leaner output file @code{example-opt.js}.
@hl.bash
haoyi-mbp:temp haoyi$ du -h target/scala-2.11/example-opt.js
diff --git a/book/src/main/scalatex/book/handson/PublishingModules.scalatex b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
index 05aa87f..9e742e2 100644
--- a/book/src/main/scalatex/book/handson/PublishingModules.scalatex
+++ b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
@@ -1,6 +1,6 @@
@import BookData._
@p
- We've spent several chapters exploring the experience of making web apps using Scala.js, but any large app (web or not!) likely relies on a host of libraries in order to implement large chunks of its functionality. Ideally these libraries would be re-usable, and can be shared among different projects, teams or even companies.
+ We've spent several chapters exploring the experience of making web apps using Scala.js, but any large application (web or not!) likely relies on a host of libraries in order to implement large chunks of its functionality. Ideally these libraries would be re-usable, and can be shared among different projects, teams or even companies.
@p
Not all code is developed in the browser. Maybe you want to run simple snippets of Scala.js which don't interact with the browser at all, and having to keep a browser open is an overkill. Maybe you want to write unit tests for your browser-destined code, so you can verify that it works without firing up Chrome. Maybe it's not a simple script but a re-distributable library, and you want to run the same command-line unit tests on both Scala.js and Scala-JVM to verify that the behavior is identical. This chapter will go through all these cases.
@@ -109,7 +109,7 @@
Publish it! Both @code{sbt publishLocal} and @code{sbt publishSigned} work on this module, for publishing either locally, Maven Central via Sonatype, or Bintray. Running the command bare should be sufficient to publish both the @code{js} or @code{jvm} projects, or you can also specify which one e.g. @code{jvm/publishLocal} to publish only one subproject.
@p
- This @code{jvm} project works identically to any other Scala-JVM project, and the @code{js} project works identically to the Command Line API described earlier. Thus you can do things like @code{fastOptStage::run} to run the code on Node.js, setting @hl.scala{requiresDOM := true}, run @code{fullOptStage::run} to run the code with full, aggressive optimizations. And of course, things that work in both Scala.js and Scala-JVM can be run on both, basic commands such as code{run} or @code{test}.
+ This @code{jvm} project works identically to any other Scala-JVM project, and the @code{js} project works identically to the Command Line API described earlier. Thus you can do things like @code{fastOptStage::run} to run the code on Node.js, setting @hl.scala{requiresDOM := true}, run @code{fullOptStage::run} to run the code with full, aggressive optimizations. And of course, things that work in both Scala.js and Scala-JVM can be run on both, basic commands such as @code{run} or @code{test}.
@p
You can also run tests using this code, if you have a testing library set up. The next section will go into detail as to how to set that up.
@@ -180,7 +180,7 @@
@hr
@p
- Now that you've got a basic cross-platform Scala module building and testing, what next? One thing you may want to do is add things to the project. Depending on where you want your code to run, there's a place for everythign:
+ Now that you've got a basic cross-platform Scala module building and testing, what next? One thing you may want to do is add things to the project. Depending on where you want your code to run, there's a place for everything:
@ul
@li
diff --git a/book/src/main/scalatex/book/handson/WebPage.scalatex b/book/src/main/scalatex/book/handson/WebPage.scalatex
index 8fbe1e8..58fceb1 100644
--- a/book/src/main/scalatex/book/handson/WebPage.scalatex
+++ b/book/src/main/scalatex/book/handson/WebPage.scalatex
@@ -9,7 +9,7 @@
@sect{Hello World: HTML}
@p
- The most basic way of building interactive web pages using Scala.js is to use the Javascript APIs to blat HTML strings directly into some container div, often @code{document.body}. This approach works, as the following code snippet demonstrates:
+ The most basic way of building interactive web pages using Scala.js is to use the Javascript APIs to blat HTML strings directly into some container @hl.html{<div>} or @hl.html{<body>}. This approach works, as the following code snippet demonstrates:
@split
@more
@@ -19,6 +19,9 @@
@BookData.example(div, "HelloWorld0().main")
@p
+ Remember that we're now requiring a @hl.scala{dom.HTMLDivElement} instead of a @hl.scala{dom.HTMLCanvsElement} to be passed in when the Javascript calls @hl.javascript{HelloWorld0().main(...)}. If you're coming to this point from the previous chapter, you'll need to update the on-page Javascript's @hl.javascript{document.getElementById} to pick a @hl.html{<div>} rather than the @hl.html{<canvas>} we were using in the previous chapter.
+
+ @p
This approach works, as the above example shows, but has a couple of disadvantages:
@ul
@@ -34,7 +37,7 @@
@p
@lnk("Scalatags", "https://github.com/lihaoyi/scalatags") is a cross-platform Scala.js/Scala-JVM library that is designed to generate HTML. To use Scalatags, you need to add it as a dependency to your Scala.js SBT project, in the @code{build.sbt} file:
- @hl.ref("examples/demos/build.sbt", "com.scalatags")
+ @hl.ref("examples/demos/build.sbt", "com.scalatags", "")
@p
With that, the above snippet of code re-written using Scalatags looks as follows:
@@ -58,7 +61,7 @@
@hl.ref("examples/demos/src/main/scala/webpage/Inputs.scala", "val box")
@less
- @BookData.example(div, "Inputs().main")
+ @BookData.example(div(height:="150px"), "Inputs().main")
@p
In Scalatags, you build up fragments of type @hl.scala{Frag} using functions like @hl.scala{div}, @hl.scala{h1}, etc., and call @hl.scala{.render} on it to turn it into a real @lnk.dom.Element. Different fragments render to different things: e.g. @hl.scala{input.render} gives you a @lnk.dom.HTMLInputElement, @hl.scala{span.render} gives you a @lnk.dom.HTMLSpanElement. You can then access the properties of these elements: adding callbacks, checking their value, anything you want.
@@ -113,11 +116,11 @@
@ul
@li
- It's safe! If you make a trivial syntactic mistake, the compiler will catch it, because Scalatags is plain Scala
+ It's safe! If you make a trivial syntactic mistake, the compiler will catch it, because Scalatags is plain Scala. Try it!
@li
It's composable! You can easily define fragments and assign them to variables, to be used later. You can break apart large Scalatags fragments the same way you break apart normal code, avoiding the huge monolithic HTML templates that are common in other templating systems.
@li
- It's Scala! You have the full power of the Scala language to write your fragments. No need to learn special syntax/cases for conditionals or repetitions: you can use plain-old-scala if-elses, for-loops, etc.
+ It's Scala! You have the full power of the Scala language to write your fragments. No need to learn special syntax/cases for conditionals or repetitions: you can use plain-old-Scala @hl.scala{if}-@hl.scala{else}s, @hl.scala{for}-loops, etc.
@p
Now that you've gotten a quick overview of the kinds of things you can do with Scalatags, let's move on to the next section of our hands-on tutorial...
@@ -135,7 +138,7 @@
@less
@BookData.example(div, "Weather0().main")
@p
- The above snippet of code uses the raw Javascript Ajax API in order to make a request to @lnk("openweathermap.org", "http://openweathermap.org/"), to get the weather data for the city of Singapore as a JSON blob. The part of the API that we'll be using is documented @lnk("here", "http://openweathermap.org/current"). For now, we're unceremoniously dumping it in a @hl.scala{pre} so you can see the raw response data.
+ The above snippet of code uses the raw Javascript Ajax API in order to make a request to @lnk("openweathermap.org", "http://openweathermap.org/"), to get the weather data for the city of Singapore as a JSON blob. The part of the API that we'll be using is documented @lnk("here", "http://openweathermap.org/current"), and if you're interested you can read all about the various options that they provide. For now, we're unceremoniously dumping it in a @hl.scala{pre} so you can see the raw response data.
@p
As you can see, using the raw Javascript API to make the Ajax call looks almost identical to actually doing this in Javascript, shown below:
@@ -181,7 +184,7 @@
@hl.ref("examples/demos/src/main/scala/webpage/Weather1.scala", "val url")
@less
- @BookData.example(div, "Weather1().main")
+ @BookData.example(div(height:="100%", overflow:="scroll"), "Weather1().main")
@p
A single call to @hl.scala{Ajax.get(...)}, with the URL, and we receive a @hl.scala{scala.concurrent.Future} that we can use to get access to the result when ready. Here we're just using it's @hl.scala{onSuccess}, but we could use it in a for-comprehension, with @lnk("Scala Async", "https://github.com/scala/async"), or however else we can use normal @hl.scala{Future}s
@@ -198,7 +201,7 @@
@hl.ref("examples/demos/src/main/scala/webpage/Weather2.scala", "Ajax.get")
@less
- @BookData.example(div, "Weather1().main")
+ @BookData.example(div(height:="100%", overflow:="scroll"), "Weather1().main")
@p
We do this by taking @hl.scala{xhr.responseText} and putting it through both @hl.scala{JSON.parse} and @hl.scala{JSON.stringify}, passing in a @hl.scala{space} argument to tell @hl.scala{JSON.stringify} to spread it out nicely.
@@ -211,7 +214,7 @@
@hl.ref("examples/demos/src/main/scala/webpage/Weather3.scala", "Ajax.get")
@less
- @BookData.example(div, "Weather3().main")
+ @BookData.example(div(height:="100%", overflow:="scroll"), "Weather3().main")
@p
First we parse the incoming response, extract a bunch of values from it, and then stick it in a Scalatags fragment for us to see. Note how we can use the names of the attributes e.g. @hl.scala{json.name} even though @hl.scala{name} is a dynamic property which you can't be sure exists: this is because @hl.scala{json} is of type @hl.scala{js.Dynamic}, which allows us to refer to arbitrary parameters and methods on the underlying object without type-checking.
diff --git a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
index 7728293..f368a71 100644
--- a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
+++ b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
@@ -302,4 +302,4 @@
@hr
@p
- Scala-Async is a Macro; that means that it is both more flexible and more limited than normal Scala, e.g. you cannot put the @hl.scala{await} call inside a lambda or higher-order-function like @hl.scala{.map}. Like Futures, it doesn't provide any fundamental capabilities, but is a tool that can be used to simplify otherwise messy asynchronous workflows.
+ Scala-Async is a Macro; that means that it is both more flexible and more limited than normal Scala, e.g. you cannot put the @hl.scala{await} call inside a lambda or higher-order-function like @hl.scala{.map}. Like Futures, it doesn't provide any fundamentally new capabilities, but is a tool that can be used to simplify otherwise messy asynchronous workflows.
diff --git a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
index 48c794c..1718f5e 100644
--- a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
+++ b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
@@ -1,74 +1,22 @@
-@sect{Background}
- @p
- Scala.js is implemented as a compiler plugin in the Scala compiler. Despite this, the overall process looks very different from that of a normal Scala application. This is because Scala.js optimizes for the size of the compiled executable, which is something that Scala-JVM does not usually do.
-
- @sect{Small Executables}
- Why do we care so much about how big our executables are in Scala.js? Why don't we care about how big they are on Scala-JVM? This is mostly due to three reasons:
-
- @ul
- @li
- When cross-compiling Scala to Javascript, the end-result tends to be much more verbose than when cross-compiled to Java Bytecode.
- @li
- Scala.js typically is run in web browsers, which typically do not work well with large executables compared to e.g. the JVM
- @li
- Scala.js often is delivered to many users over the network, and long download times force users to wait, degrading the user experience
-
- @p
- These factors combined means that Scala.js has to put in extra effort to optimize the code to reduce it's size at compile-time.
-
- @sect{Raw Verbosity}
- @p
- Scala.js compiles to Javascript source code, while Scala-JVM compiles to Java bytecode. Java bytecode is a binary format and thus somewhat optimized for size, while Javascript is textual and is designed to be easy to read and write by hand.
- @p
- What does these mean, concretely? This means that a symbol marking something, e.g. the start of a function, is often a single byte in Java bytecode. Even more, it may not have any delimiter at all, instead the meaning of the binary data being inferred from its position in the file! On the other hand, in Javascript, declaring a function takes a long-and-verbose @hl.javascript{function} keyword, which together with peripheral punctuation (@code{.}, @code{ = }, etc.) often adds up to tens of bytes to express a single idea.
- @p
- What does this mean concretely? This means that expressing the same meaning in Javascript usually takes more "raw code" than expressing the same meaning in Java bytecode. Even though Java bytecode is relatively verbose for a binary format, it still is significantly more concise the Javascript, and it shows: the Scala standard library weighs in at a cool 6mb on Scala-JVM, while it weighs 20mb on Scala.js.
- @p
- All things being equal, this would mean that Scala.js would have to work harder to keep down code-size than Scala-JVM would have to. Alas, not all other things are equal.
-
- @sect{Browsers Performance}
- @p
- Without any optimization, a naive compilation to Scala.js results in an executable (Including the standard library) weighing around 20mb. On the surface, this isn't a problem: runtimes like the JVM have no issue with loading 20mb of Java bytecode to execute; many large desktop applications weigh in the 100s of megabytes while still loading and executing fine.
- @p
- However, the web browser isn't a native execution environment; loading 20mb of Javascript is sufficient to heavily tax even the most modern web browsers such as Chrome and Firefox. Even though most of the code comprises class and method definitions that never have their contents executed, loading such a heavy load into e.g. Chrome makes it freeze for 5-10 seconds initially. Even after that, even after the code has all been parsed and isn't been actively executed, having all this Javascript makes the browser sluggish for up to a minute before the JIT compiler can speed things up.
- @p
- Overall, this means that you probably do not want to work with un-optimized Scala.js executables. Even for development, the slow load times and initial sluggishness make testing the results of your hard-work in the browser a frustrating experience. But that's not all...
-
- @sect{Deployment Size}
- @p
- Scala.js applications often run in the browser. Not just any browser, but the browsers of your users, who had come to your website or web-app to try and accomplish some task. This is in stark contrast the Scala-JVM applications, which most often run on servers: servers that you own and control, and can deploy code to at your leisure.
-
- @p
- When running code on your own servers in some data center, you often do not care how big the compiled code is: the Scala standard library is several (6-7) megabytes, which added to your own code and any third-party libraries you're using, may add up to tens of megabytes, maybe a hundred or two if it's a relatively large application. Even that pales in comparison to the size of the JVM, which weighs in the 100s of megabytes.
- @p
- Even so, you are deploying your code on an machine (virtual or real) which has several gigabytes of memory and 100s of gigabytes of disk space. Even if the size of the code makes deployment slower, you only deploy fresh code a handful of times a day at most, and the size of your executable typically does not worry you.
- @p
- Scala.js is different: it runs in the browsers of your users. Before it can run in their browser, it first has to be downloaded, probably over a connection that is much slower than the one used to deploy your code to your servers or data-center. It probably is downloaded thousands of times per day, and every user which downloads it must pay the cost of waiting for it to finish downloading before they can take any actions on your website.
-
- @p
- A typical website loads ~100kb-1mb of Javascript, and 1mb is on the heavy side. Most Javascript libraries weigh in on the order of 50-100kb. For Scala.js to be useful in the browser, it has to be able to compare favorably with these numbers.
-
- @hr
+@p
+ Scala.js is implemented as a compiler plugin in the Scala compiler. Despite this, the overall process looks very different from that of a normal Scala application. This is because Scala.js optimizes for the size of the compiled executable, which is something that Scala-JVM does not usually do.
- @p
- Thus, while on Scala-JVM you typically have executables that (including dependencies) end up weighing 10s to 100s of megabytes, Scala.js has a much tighter budget. A hello world Scala.js application weighs in at around 100kb, and as you write more code and use more libraries (and parts of the standard library) this number rises to the 100s of kb. This isn't tiny, especially compared to the many small Javascript libraries out there, but it definitely is much smaller than what you'd be used to on the JVM.
-
- @sect{Whole Program Optimizaton}
- @p
- At a first approximation, Scala.js achieves its tiny executables by using whole-program optimization. Scala-JVM, like Java, allows for separate compilation: this means that after compilation, you can combine your compiled code with code compiled separately, which can interact with the code you already compiled in an ad-hoc basis: code from both sides can call each others methods, instantiate each others classes, etc. without any limits.
+@sect{Whole Program Optimizaton}
+ @p
+ At a first approximation, Scala.js achieves its tiny executables by using whole-program optimization. Scala-JVM, like Java, allows for separate compilation: this means that after compilation, you can combine your compiled code with code compiled separately, which can interact with the code you already compiled in an ad-hoc basis: code from both sides can call each others methods, instantiate each others classes, etc. without any limits.
- @p
- Even things like package-private do not help you: Java packages are separate-compile-able too, and multiple compilation runs can dump things in the same package! You may think that private members and methods may be some salvation, but the Java ecosystem typically relies heavily on reflection, which depends on the fact that these private things remain exactly as-they-are.
+ @p
+ Even things like package-private do not help you: Java packages are separate-compile-able too, and multiple compilation runs can dump things in the same package! You may think that private members and methods may be some salvation, but the Java ecosystem typically relies heavily on reflection, which depends on the fact that these private things remain exactly as-they-are.
- @p
- Overall, this makes it difficult to do any meaningful optimization: you never know whether or not you can eliminate a class, method or field. Even if it's not used anywhere you can see, it could easily be used by some other code compiled separately, or accessed through reflection.
+ @p
+ Overall, this makes it difficult to do any meaningful optimization: you never know whether or not you can eliminate a class, method or field. Even if it's not used anywhere you can see, it could easily be used by some other code compiled separately, or accessed through reflection.
- @p
- With Scala.js, we have decided to forgo reflection, and forgo separate compilation, in exchange for smaller executables. This is made easier by the fact that the pure-Scala ecosystem makes little use of reflection overall. Thus, at the right before shipping your Scala.js app to your users, the Scala.js optimizer gathers up all your Scala.js code, determines which things are used and which are not, and eliminates all the un-used classes/methods/variables. This allows us to achieve a much smaller code size than is possible with reflection/separate-compilation support. Furthermore, because we forgo these two things, we can perform much more aggressive inlining and other compile-time optimizations than is possible with Scala-JVM, further reducing code size and improving performance.
+ @p
+ With Scala.js, we have decided to forgo reflection, and forgo separate compilation, in exchange for smaller executables. This is made easier by the fact that the pure-Scala ecosystem makes little use of reflection overall. Thus, at the right before shipping your Scala.js app to your users, the Scala.js optimizer gathers up all your Scala.js code, determines which things are used and which are not, and eliminates all the un-used classes/methods/variables. This allows us to achieve a much smaller code size than is possible with reflection/separate-compilation support. Furthermore, because we forgo these two things, we can perform much more aggressive inlining and other compile-time optimizations than is possible with Scala-JVM, further reducing code size and improving performance.
- @p
- It's worth noting that such optimizations exist as an option on the JVM aswell: @lnk("Proguard", "http://proguard.sourceforge.net/") is a well known library for doing similar DCE/optimization for Java/Scala applications, and is extensively used in developing mobile applications which face similar "minimize-code-size" constraints that web-apps do. However, the bulk of Scala code which runs on the server does not use these tools.
+ @p
+ It's worth noting that such optimizations exist as an option on the JVM aswell: @lnk("Proguard", "http://proguard.sourceforge.net/") is a well known library for doing similar DCE/optimization for Java/Scala applications, and is extensively used in developing mobile applications which face similar "minimize-code-size" constraints that web-apps do. However, the bulk of Scala code which runs on the server does not use these tools.
@sect{How Compilation Works}
@p
@@ -227,3 +175,11 @@
@p
Empirically, running GCC on the output of the optimizer produces the smallest output blobs: ~150-400kb, significantly smaller than the output of running either of them alone, and so that is what we do. This takes 5-10 seconds to run, which makes it somewhat slow for iterative development, so it's typically only run right before final testing and deployment. This corresponds to the @code{fullOptJS} command in SBT.
+
+@hr
+
+@p
+ This hopefully has given a good overview of how the Scala.js compilation pipeline works. The pipeline and optimizer is a work-in-progress, and is changing all the time in an attempt to achieve ever-smaller executables and ever-faster code.
+
+@p
+ This whole chapter has been focused on the @i{what} but not the @i{why}. The chapter on @sect.ref{Scala.js' Design Space} contains a section which talks about @sect.ref("Small Executables", "why we care so much about small executables"). \ No newline at end of file
diff --git a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
index 2adb0be..a1e807f 100644
--- a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
+++ b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
@@ -187,14 +187,53 @@
@p
This is a common pattern in situations where there's a tradeoff between debuggability and speed. In Scala.js' case, it allows us to get good debuggability in development, as well as good performance in production. There's some loss in debuggability in development, sacrificed in exchange for greater performance.
+@sect{Small Executables}
+ Why do we care so much about how big our executables are in Scala.js? Why don't we care about how big they are on Scala-JVM? This is mostly due to three reasons:
+
+ @ul
+ @li
+ When cross-compiling Scala to Javascript, the end-result tends to be much more verbose than when cross-compiled to Java Bytecode.
+ @li
+ Scala.js typically is run in web browsers, which typically do not work well with large executables compared to e.g. the JVM
+ @li
+ Scala.js often is delivered to many users over the network, and long download times force users to wait, degrading the user experience
-@sect("Why Jars instead of RequireJS/CommonJS")
- @p
- In JVM-land, the standard method for distributing these libraries is as @lnk("Maven Artifacts", "http://stackoverflow.com/questions/2487485/what-is-maven-artifact"). These are typically published in a public location such as @lnk("Maven Central", "http://search.maven.org/"), where others can find and download them for use. Typically downloads are done automatically by the build-tool: in Scala-JVM typically this is SBT.
- @p
- In Javascript-land, there are multiple ways of acquiring dependencies: @lnk("CommonJS", "http://en.wikipedia.org/wiki/CommonJS") and @lnk("RequireJS/AMD", "http://requirejs.org/") are two competing standards with a host of implementations. Historically, a third approach has been most common: the developer would simply download the modules himself, check it into source-control and manually add a @hl.html{<script>} tag to the HTML page that will make the functionality available through some global variable.
@p
- In Scala.js, we side with the JVM standard of distributing libraries as maven jars. This lets us take advantage of all the existing tooling around Scala to handle these jars (SBT, Ivy, Maven Central, etc.) which is far more mature and cohesive than the story in Javascript-land. For example, the Scalatags library we used in the earlier is @lnk("published on maven central", "http://search.maven.org/#search%7Cga%7C1%7Cscalatags"), and adding one line to SBT is enough to pull it down and include it in our project.
+ These factors combined means that Scala.js has to put in extra effort to optimize the code to reduce it's size at compile-time.
+
+ @sect{Raw Verbosity}
+ @p
+ Scala.js compiles to Javascript source code, while Scala-JVM compiles to Java bytecode. Java bytecode is a binary format and thus somewhat optimized for size, while Javascript is textual and is designed to be easy to read and write by hand.
+ @p
+ What does these mean, concretely? This means that a symbol marking something, e.g. the start of a function, is often a single byte in Java bytecode. Even more, it may not have any delimiter at all, instead the meaning of the binary data being inferred from its position in the file! On the other hand, in Javascript, declaring a function takes a long-and-verbose @hl.javascript{function} keyword, which together with peripheral punctuation (@code{.}, @code{ = }, etc.) often adds up to tens of bytes to express a single idea.
+ @p
+ What does this mean concretely? This means that expressing the same meaning in Javascript usually takes more "raw code" than expressing the same meaning in Java bytecode. Even though Java bytecode is relatively verbose for a binary format, it still is significantly more concise the Javascript, and it shows: the Scala standard library weighs in at a cool 6mb on Scala-JVM, while it weighs 20mb on Scala.js.
+ @p
+ All things being equal, this would mean that Scala.js would have to work harder to keep down code-size than Scala-JVM would have to. Alas, not all other things are equal.
+
+ @sect{Browsers Performance}
+ @p
+ Without any optimization, a naive compilation to Scala.js results in an executable (Including the standard library) weighing around 20mb. On the surface, this isn't a problem: runtimes like the JVM have no issue with loading 20mb of Java bytecode to execute; many large desktop applications weigh in the 100s of megabytes while still loading and executing fine.
+ @p
+ However, the web browser isn't a native execution environment; loading 20mb of Javascript is sufficient to heavily tax even the most modern web browsers such as Chrome and Firefox. Even though most of the code comprises class and method definitions that never have their contents executed, loading such a heavy load into e.g. Chrome makes it freeze for 5-10 seconds initially. Even after that, even after the code has all been parsed and isn't been actively executed, having all this Javascript makes the browser sluggish for up to a minute before the JIT compiler can speed things up.
+ @p
+ Overall, this means that you probably do not want to work with un-optimized Scala.js executables. Even for development, the slow load times and initial sluggishness make testing the results of your hard-work in the browser a frustrating experience. But that's not all...
+
+ @sect{Deployment Size}
+ @p
+ Scala.js applications often run in the browser. Not just any browser, but the browsers of your users, who had come to your website or web-app to try and accomplish some task. This is in stark contrast the Scala-JVM applications, which most often run on servers: servers that you own and control, and can deploy code to at your leisure.
+
+ @p
+ When running code on your own servers in some data center, you often do not care how big the compiled code is: the Scala standard library is several (6-7) megabytes, which added to your own code and any third-party libraries you're using, may add up to tens of megabytes, maybe a hundred or two if it's a relatively large application. Even that pales in comparison to the size of the JVM, which weighs in the 100s of megabytes.
+ @p
+ Even so, you are deploying your code on an machine (virtual or real) which has several gigabytes of memory and 100s of gigabytes of disk space. Even if the size of the code makes deployment slower, you only deploy fresh code a handful of times a day at most, and the size of your executable typically does not worry you.
+ @p
+ Scala.js is different: it runs in the browsers of your users. Before it can run in their browser, it first has to be downloaded, probably over a connection that is much slower than the one used to deploy your code to your servers or data-center. It probably is downloaded thousands of times per day, and every user which downloads it must pay the cost of waiting for it to finish downloading before they can take any actions on your website.
+
+ @p
+ A typical website loads ~100kb-1mb of Javascript, and 1mb is on the heavy side. Most Javascript libraries weigh in on the order of 50-100kb. For Scala.js to be useful in the browser, it has to be able to compare favorably with these numbers.
+
+ @hr
@p
- One interesting wrinkle in Scala.js's case is that since Scala can compile to both Scala.js and Scala-JVM, it is entirely possible to publish a library that can run on both client and server! This chapter will explore the process of building, testing, and publishing such a library. \ No newline at end of file
+ Thus, while on Scala-JVM you typically have executables that (including dependencies) end up weighing 10s to 100s of megabytes, Scala.js has a much tighter budget. A hello world Scala.js application weighs in at around 100kb, and as you write more code and use more libraries (and parts of the standard library) this number rises to the 100s of kb. This isn't tiny, especially compared to the many small Javascript libraries out there, but it definitely is much smaller than what you'd be used to on the JVM.
diff --git a/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex b/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
index cf14057..ebfd1db 100644
--- a/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
+++ b/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
@@ -68,10 +68,17 @@
@p
Calling toString on a Float or a Double that holds an integral value, will not append ".0" to that value:
- @hl.scala
- println(1.0)
- // Scala: 1.0
- // Scala.js: 1
+ @split
+ @half
+ @hl.scala
+ // Scala-JVM:
+ > println(1.0)
+ 1.0
+ @half
+ @hl.scala
+ // Scala.js:
+ > println(1.0)
+ 1
@p
This is due to how numeric values are represented at runtime in Scala.js: @hl.scala{Float}s and @hl.scala{Double}s are raw Javascript @hl.scala{Number}s, and their @hl.scala{toString} behavior follows from that.
@@ -86,11 +93,11 @@
@sect{Unit}
@p
- scala.Unit is represented using JavaScript's undefined. Therefore, calling @hl.scala{toString()} on @hl.scala{Unit} will return @hl.scala{"undefined"} rather than @hl.scala{"()""}.
+ @hl.scala{scala.Unit} is represented using JavaScript's undefined. Therefore, calling @hl.scala{toString()} on @hl.scala{Unit} will return @hl.scala{"undefined"} rather than @hl.scala{"()"}. In practice, this shouldn't matter for most use cases.
@sect{Reflection}
@p
- Java reflection and Scala reflection, are not supported. There is limited support for @lnk("java.lang.Class", "https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html"), e.g., obj.getClass.getName will work for any Scala.js object (not for objects that come from JavaScript interop). Reflection makes it difficult to perform the optimizations that Scala.js heavily relies on. For a more detailed discussion on this topic, take a look at the section @sect.ref{Why No Reflection?}.
+ Java reflection and Scala reflection, are not supported. There is limited support for @lnk("java.lang.Class", "https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html"), e.g., @hl.scala{obj.getClass.getName} will work for any Scala.js object but not for objects that come from JavaScript interop (i.e. anything which @hl.scala{extends js.Object}). Reflection makes it difficult to perform the optimizations that Scala.js heavily relies on. For a more detailed discussion on this topic, take a look at the section @sect.ref{Why No Reflection?}.
@sect{Exceptions}
@p
@@ -98,11 +105,11 @@
@ul
@li
- @hl.scala{ArrayIndexOutOfBoundsException} is never thrown.
+ @hl.scala{ArrayIndexOutOfBoundsException} is never thrown. Instead, the value becomes @hl.javascript{undefined}, which will probably propagate through your program and blow up somewhat later.
@li
@hl.scala{NullPointerException} is reported as JavaScript @hl.scala{TypeError} instead.
@li
- @hl.scala{StackOverflowError} is unsupported since the underlying JavaScript exception type varies based on the browser.
+ @hl.scala{StackOverflowError} is unsupported since the underlying JavaScript exception type varies based on the browser. e.g. in Chrome the browser just hangs and kills the process/tab without any chance for the developer to catch the error.
@sect{Regular expressions}
@p
@@ -111,7 +118,7 @@
This sometimes has an impact on functions in the Scala library that use regular expressions themselves. A list of known functions that are affected is given here:
@ul
@li
- @hl.scala{StringLike.split(x: Array[Char])} (see issue #105)
+ @hl.scala{StringLike.split(x: Array[Char])}
@sect{Symbols}
@p
@@ -139,7 +146,7 @@
@p
Calls to either of these two methods which could not be rewritten, or calls to constructors of the protected Val class without an explicit name as parameter, will issue a warning.
@p
- Note that the name rewriting honors the nextName iterator. Therefore, the full rewrite is:
+ Note that the name rewriting honors the @hl.scala{nextName} iterator. Therefore, the full rewrite is:
@hl.scala
val <ident> = Value(
if (nextName != null && nextName.hasNext)
@@ -152,15 +159,15 @@
@sect{Library Differences}
@val myTable = Seq(
- ("Most of java.lang.*", "j.l.Thread, j.l.Runtime, ..."),
- ("Almost all of scala.*", "s.c.parallel, s.tools.nsc"),
+ ("Most of java.lang.*", "java.lang.Thread, java.lang.Runtime, ..."),
+ ("Almost all of scala.*", "scala.collection.parallel, scala.tools.nsc"),
("Some of java.util.*", "org.omg.CORBA, sun.misc.*"),
- ("Scala Macros: upickle, scala-async, scalaxy, etc", "Reflection: scala-pickling, scala-reflect"),
- ("Pure-Scala ecosystem: shapeless, scalaz, scalatags, utest", "Java-dependent: Scalatest, Scalate"),
- ("JS stuff: XmlHttpRequest, Websockets. Localstorage", " JVM stuff: Netty, akka, spray, file IO, JNI"),
+ ("Macros: uPickle, Scala-Async, Scalaxy, etc", "Reflection: Scala-Pickling, Scala-Reflect"),
+ ("Shapeless, Scalaz, Scalatags, uTest", "Scalatest, Scalate"),
+ ("XMLHttpRequest, Websockets. Localstorage", "Netty, Akka, Spray, File IO, JNI"),
("HTML DOM, Canvas, WebGL", "AWT, Swing, SWT, OpenGL"),
- ("JavaScript libraries: chipmunk.js, hand.js, react.js, jquery", "Java ecosystem: guice, junit, apache-commons, log4j"),
- ("IntelliJ, Eclipse, SBT, Chrome console, firebug", "Scala REPL, Yourkit, VisualVM, JProfiler")
+ ("Chipmunk.js, Hand.js, React.js, jQuery", "Guice, JUnit, Apache-Commons, log4j"),
+ ("IntelliJ, Eclipse, SBT, Chrome console, Firebug", "Scala REPL, Yourkit, VisualVM, JProfiler")
)
@p
@@ -189,7 +196,7 @@
@p
There isn't a full list of standard library library APIs which are available from Scala.js, but it should be enough to give you a rough idea of what is supported. The full list of classes that have been ported to Scala.js is available under @sect.ref{Available Java APIs}
- @sect{Reflection v.s. Macros}
+ @sect{Macros v.s. Reflection}
@tableHead
@for(tuple <- myTable.slice(3, 4))
@tr
@@ -199,7 +206,7 @@
As described @sect.ref("Why No Reflection?", "here"), Reflection is not supported in Scala.js, due to the way it inhibits optimization. This doesn't just mean you can't use reflection yourself: many third-party libraries also use reflection, and you won't be able to use them either.
@p
- On the other hand, Scala.js does support Macros, and macros can in many ways substitute many of the use cases that people have traditionally used reflection for (see @sect.ref("Macros", "here"). For example, instead of using a reflection-based serialization library like @lnk.github.scalaPickling, you can use a macro-based library such as @lnk.github.uPickle.
+ On the other hand, Scala.js does support Macros, and macros can in many ways substitute many of the use cases that people have traditionally used reflection for (see @sect.ref("Macros", "here")). For example, instead of using a reflection-based serialization library like @lnk.github.scalaPickling, you can use a macro-based library such as @lnk.github.uPickle.
@sect{Pure-Scala v.s. Java Libraries}
@tableHead
@@ -225,7 +232,7 @@
Naturally, none of these are an exact replacement, as the browser environment is fundamentally different from that of a desktop application running on the JVM. Nonetheless, there are many analogues, and if so desired you can write code to abstract away these differences and run on both Scala.js and Scala-JVM
- @sect{Java tooling v.s. Scala/Browser tooling}
+ @sect{Scala/Browser tooling v.s. Java tooling}
@tableHead
@for(tuple <- myTable.slice(7, 8))
@tr
diff --git a/examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala b/examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala
index f3a23f9..d3d2f91 100644
--- a/examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala
+++ b/examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala
@@ -1,3 +1,3 @@
-/*shared/main/scala/simple/Simple.scala*/
package simple
+
case class FileData(name: String, size: Long) \ No newline at end of file
diff --git a/examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala b/examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala
index c008a87..adefe01 100644
--- a/examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala
+++ b/examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala
@@ -1,4 +1,3 @@
-//js/src/main/scala/simple/Platform.scala
package simple
import scalatags.JsDom.all._
diff --git a/examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala b/examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala
index f10eede..026e52d 100644
--- a/examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala
+++ b/examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala
@@ -1,4 +1,3 @@
-//js/src/main/scala/simple/Platform.scala
package simple
import scalatags.JsDom.all._
import org.scalajs.dom
diff --git a/examples/demos/src/main/scala/canvasapp/FlappyLine.scala b/examples/demos/src/main/scala/canvasapp/FlappyLine.scala
index 02cb91b..fdf6e60 100644
--- a/examples/demos/src/main/scala/canvasapp/FlappyLine.scala
+++ b/examples/demos/src/main/scala/canvasapp/FlappyLine.scala
@@ -91,7 +91,7 @@ object FlappyLine extends{
obstacles.clear()
dead -= 1
renderer.fillStyle = "darkred"
- renderer.fillText("You Died", canvas.width / 2, canvas.height / 2)
+ renderer.fillText("Game Over", canvas.width / 2, canvas.height / 2)
}
def run() = {