summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2015-03-28 22:16:57 +0800
committerLi Haoyi <haoyi@dropbox.com>2015-03-28 22:16:57 +0800
commit1ac4a2594bf2ebe1fc80b93f1208343653892517 (patch)
tree7f07cee1d4e6a608853c6f862550675165b78dfb
parent7006cebd061784900233c405ce03665151b86010 (diff)
downloadhands-on-scala-js-1ac4a2594bf2ebe1fc80b93f1208343653892517.tar.gz
hands-on-scala-js-1ac4a2594bf2ebe1fc80b93f1208343653892517.tar.bz2
hands-on-scala-js-1ac4a2594bf2ebe1fc80b93f1208343653892517.zip
scalatex 0.2.1
-rw-r--r--book/src/main/scala/book/BookData.scala29
-rw-r--r--book/src/main/scala/book/Main.scala45
-rw-r--r--book/src/main/scala/book/Utils.scala14
-rw-r--r--book/src/main/scalatex/Index.scalatex2
-rw-r--r--book/src/main/scalatex/Intro.scalatex4
-rw-r--r--book/src/main/scalatex/handson/CanvasApp.scalatex10
-rw-r--r--book/src/main/scalatex/handson/ClientServer.scalatex4
-rw-r--r--book/src/main/scalatex/handson/CommandLine.scalatex26
-rw-r--r--book/src/main/scalatex/handson/GettingStarted.scalatex20
-rw-r--r--book/src/main/scalatex/handson/PublishingModules.scalatex6
-rw-r--r--book/src/main/scalatex/handson/WebPage.scalatex36
-rw-r--r--book/src/main/scalatex/indepth/AdvancedTechniques.scalatex2
-rw-r--r--book/src/main/scalatex/indepth/CompilationPipeline.scalatex8
-rw-r--r--book/src/main/scalatex/indepth/DesignSpace.scalatex16
-rw-r--r--book/src/main/scalatex/indepth/JavaAPIs.scalatex2
-rw-r--r--build.sbt8
-rw-r--r--examples/demos/src/main/scala/scrollmenu/Controller.scala2
-rw-r--r--project/build.sbt2
18 files changed, 102 insertions, 134 deletions
diff --git a/book/src/main/scala/book/BookData.scala b/book/src/main/scala/book/BookData.scala
index 9fa4d5e..dc4d717 100644
--- a/book/src/main/scala/book/BookData.scala
+++ b/book/src/main/scala/book/BookData.scala
@@ -3,15 +3,18 @@ package book
import java.io.File
import acyclic.file
-import ammonite.all._
+import ammonite.ops._
import ammonite.ops.Path
import scalatags.Text.TypedTag
import scalatags.Text.all._
+import scalatex.site
+import scalatex.site.Highlighter
+
object BookData {
- val wd = processWorkingDir
+ val wd = cwd
val cloneRoot = Path(System.getProperty("clone.root"))
-
-
+ val lnk = book.lnk
+ val pureTable = book.pureTable
lazy val javaAPIs = {
import java.io.File
@@ -48,23 +51,13 @@ object BookData {
def less = div(cls:="pure-u-1 pure-u-md-11-24")
def half = div(cls:="pure-u-1 pure-u-md-1-2")
-
- val hl = new scalatex.site.Highlighter {
- override val pathMappings = Seq(
+ lazy val hl = new Highlighter {
+ override def pathMappings = Seq(
cloneRoot/"scala-js" -> "https://github.com/scala-js/scala-js/blob/master",
cloneRoot/"workbench-example-app" -> "https://github.com/lihaoyi/workbench-example-app/blob/master",
wd -> "https://github.com/lihaoyi/hands-on-scala-js/blob/master"
)
- override val suffixMappings = Map(
- "scala" -> "scala",
- "sbt" -> "scala",
- "js" -> "javascript"
- )
- def scala(s: String) = this.highlight(s, "scala")
- def bash(s: String) = this.highlight(s, "bash")
- def html(s: String) = this.highlight(s, "html")
- def xml(s: String) = this.highlight(s, "xml")
- def diff(s: String) = this.highlight(s, "diff")
- def javascript(s: String) = this.highlight(s, "javascript")
}
+
+ val sect = new site.Section{}
}
diff --git a/book/src/main/scala/book/Main.scala b/book/src/main/scala/book/Main.scala
index 737c130..7586e54 100644
--- a/book/src/main/scala/book/Main.scala
+++ b/book/src/main/scala/book/Main.scala
@@ -7,12 +7,14 @@ import ammonite.ops.Path
import scalatags.Text.{attrs, tags2, all}
import scalatags.Text.all._
-import scalatex.site.Section.Tree
-import scalatex.site.Site
-import ammonite.all.{rel => _, _}
+import scalatex.site
+
+import scalatex.site.{Tree, Site}
+import ammonite.ops._
object Main {
- val wd = processWorkingDir
+ val wd = cwd
+
def main(args: Array[String]): Unit = {
val googleAnalytics =
"""
@@ -25,12 +27,15 @@ object Main {
| ga('send', 'pageview');
""".stripMargin
- def data = upickle.write(sect.structure)
+
val s = new Site {
- def content = Map("index.html" -> Index())
+ def content = Map("index.html" -> scalatex.Index())
- override def autoResources = super.autoResources ++ Seq(
+ override def autoResources =
+ super.autoResources ++
+ BookData.hl.autoResources ++
+ site.Sidebar.autoResources ++ Seq(
root/"META-INF"/'resources/'webjars/'pure/"0.5.0"/"grids-responsive-min.css",
root/'css/"side-menu.css",
root/"example-opt.js",
@@ -56,25 +61,9 @@ object Main {
script(raw(googleAnalytics))
)
override def bodyFrag(frag: Frag) = body(
-
- div(id:="layout")(
- a(href:="#menu", id:="menuLink", cls:="menu-link")(
- span
- ),
- div(id:="menu")
-
- ),
- div(
- id:="main",
- div(
- id:="main-box",
- cls:="scalatex-content",
- maxWidth:="840px",
- lineHeight:="1.6em",
- frag
- )
- ),
- onload:=s"scrollmenu.Controller().main($data)"
+ super.bodyFrag(frag),
+ site.Sidebar.snippet(BookData.sect.structure.children),
+ scalatex.site.Highlighter.snippet
)
}
@@ -85,7 +74,7 @@ object Main {
def rec(n: Tree[String]): Seq[String] = {
n.value +: n.children.flatMap(rec)
}
- rec(sect.structure).toSet
+ rec(BookData.sect.structure).toSet
}
val dupes = allNames.groupBy(x => x)
.values
@@ -95,7 +84,7 @@ object Main {
assert(dupes.size == 0, s"Duplicate names: $dupes")
- val dangling = sect.usedRefs -- allNames
+ val dangling = BookData.sect.usedRefs -- allNames
assert(dangling.size == 0, s"Dangling Refs: $dangling")
diff --git a/book/src/main/scala/book/Utils.scala b/book/src/main/scala/book/Utils.scala
index c62f71f..5698dd8 100644
--- a/book/src/main/scala/book/Utils.scala
+++ b/book/src/main/scala/book/Utils.scala
@@ -13,20 +13,6 @@ case class pureTable(header: Frag*){
)
}
}
-object sect extends scalatex.site.Section{
- var indent = 0
-
- override val headers: Seq[Header] = Seq(
- Header(
- (l, h, s) => div(cls:="header")(h1(h, l), br, h2(s)),
- f => div(cls:="content", f)
- ),
- Header(
- (l, h, s) => div(cls:="header")(h1(id:=munge(h), h, l), br)),
- h1, h2, h3, h4, h5, h6
- )
-}
-
object lnk{
val usedLinks = mutable.Set.empty[String]
diff --git a/book/src/main/scalatex/Index.scalatex b/book/src/main/scalatex/Index.scalatex
index e9f65bb..72f9668 100644
--- a/book/src/main/scalatex/Index.scalatex
+++ b/book/src/main/scalatex/Index.scalatex
@@ -12,7 +12,7 @@ is a set of detailed expositions on various parts of the Scala.js platform. Noth
@hl.ref(wd/'examples/'demos/'src/'main/'scala/"Splash.scala", "var x")
@less
- @BookData.example(canvas, "Splash().main")
+ @example(canvas, "Splash().main")
@p
@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.
diff --git a/book/src/main/scalatex/Intro.scalatex b/book/src/main/scalatex/Intro.scalatex
index b72b6e2..e4ab5bd 100644
--- a/book/src/main/scalatex/Intro.scalatex
+++ b/book/src/main/scalatex/Intro.scalatex
@@ -15,7 +15,7 @@
}
@half
- @hl.javascript
+ @hl.js
ScalaJS.c.LMain$.prototype.main__V = (function() {
var x = 0;
while ((x < 10)) {
@@ -111,7 +111,7 @@
@p
At a first approximation, Scala.js provides you a sane language to do development in the web browser. This saves you from an endless stream of Javascript warts like this one:
- @hl.javascript
+ @hl.js
javascript> ["10", "10", "10", "10"].map(parseInt)
[10, NaN, 2, 3] // WTF
diff --git a/book/src/main/scalatex/handson/CanvasApp.scalatex b/book/src/main/scalatex/handson/CanvasApp.scalatex
index d008508..c485610 100644
--- a/book/src/main/scalatex/handson/CanvasApp.scalatex
+++ b/book/src/main/scalatex/handson/CanvasApp.scalatex
@@ -35,7 +35,7 @@
@hl.ref(canvasapp/"ScratchPad.scala", "/*code*/")
@less
- @BookData.example(canvas, "canvasapp.ScratchPad().main")
+ @book.BookData.example(canvas, "canvasapp.ScratchPad().main")
@p
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!
@@ -69,7 +69,7 @@
@hl.ref(canvasapp/"Clock.scala", "/*code*/")
@less
- @BookData.example(canvas, "canvasapp.Clock().main")
+ @book.BookData.example(canvas, "canvasapp.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}. Again, click on the link @i(cls:="fa fa-link ") icon to view the full-code if you're having trouble here.
@@ -151,7 +151,7 @@
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!
@div
- @BookData.example(canvas, "canvasapp.FlappyLine().main")
+ @book.BookData.example(canvas, "canvasapp.FlappyLine().main")
@sect{Canvas Recap}
@p
@@ -180,9 +180,9 @@
@li
@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.
+ @hl.scala{frame} is a Scala @hl.scala{Int}, and obeys Scala semantics, though it is implemented as a Javascript @hl.js{Number} under the hood.
@li
- @hl.scala{playerY} and @hl.scala{playerV} are Scala @hl.scala{Double}s, implemented directly as Javascript @hl.javascript{Number}s
+ @hl.scala{playerY} and @hl.scala{playerV} are Scala @hl.scala{Double}s, implemented directly as Javascript @hl.js{Number}s
@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!
diff --git a/book/src/main/scalatex/handson/ClientServer.scalatex b/book/src/main/scalatex/handson/ClientServer.scalatex
index 4d15735..eef99b3 100644
--- a/book/src/main/scalatex/handson/ClientServer.scalatex
+++ b/book/src/main/scalatex/handson/ClientServer.scalatex
@@ -28,7 +28,7 @@
@p
Getting started with client-server integration, let's go with the simplest configuration possible: a Spray server and a Scala.js client. Most of the other web-frameworks (@lnk.misc.Play, @lnk.misc.Scalatra, etc.) will have more complex configurations, but the basic mechanism of wiring up Scala.js to your web framework will be the same. Just like our project in @sect.ref{Cross Publishing Libraries}, our project will look like this:
- @hl.bash
+ @hl.sh
$ tree
.
├── build.sbt
@@ -68,7 +68,7 @@
@p
This is a typical @lnk.github.Scalatags HTML snippet. Note that since we're serving it directly from the server in Scala code, we do not need to leave a @code{.html} file somewhere on the filesystem! We can declare all HTML, including the skeleton of the page, in Scalatags. Otherwise it's the same as what we saw in earlier chapters: A simple HTML page which includes a script tag to run our Scala.js application.
@p
- Lastly, we'll set up the Scala.js main method, which we are calling in the @hl.html{<script>} tag above to kick off the client-side application.
+ Lastly, we'll set up the Scala.js main method, which we are calling in the @hl.xml{<script>} tag above to kick off the client-side application.
@hl.ref(client/"Client.scala")
diff --git a/book/src/main/scalatex/handson/CommandLine.scalatex b/book/src/main/scalatex/handson/CommandLine.scalatex
index 26e0811..d3b888c 100644
--- a/book/src/main/scalatex/handson/CommandLine.scalatex
+++ b/book/src/main/scalatex/handson/CommandLine.scalatex
@@ -29,7 +29,7 @@
@p
Now, let's open up your SBT console in your Scala.js app
-@hl.bash
+@hl.sh
>
@p
@@ -40,13 +40,13 @@
The most fundamental thing you can do in the Scala.js CLI is to compile your code. Let's go through the various mechanisms of "compiling" things:
@sect{The compile Command}
- @hl.bash
+ @hl.sh
> compile
@p
Just as you can @code{compile} Scala-JVM projects, you can also @code{compile} Scala.js projects. Like compiling Scala-JVM projects, this leaves a bunch of @code{.class} files in your @code{target} directory. Unlike Scala-JVM projects, this also leaves a bunch of @code{.sjsir} files, which correspond to your Scala.js output files:
- @hl.bash
+ @hl.sh
classes
└── example
├── Point$.class
@@ -63,12 +63,12 @@
However, unlike on Scala-JVM, you cannot directly run the @code{.sjsir} files spat out by the Scala.js compiler. These files are an Intermediate Representation, which needs to go through the next step in the compilation pipeline before being turned into Javascript.
@sect{The package Command}
- @hl.bash
+ @hl.sh
> package
@p
Also like on Scala-JVM, Scala.js also supports the @code{package} command. This command generates a @code{.jar} like it does in Scala-JVM, except this version appends this weird @code{_sjs0.6} suffix.
- @hl.bash
+ @hl.sh
target/scala-2.11/
└── example_sjs0.6_2.11-0.1-SNAPSHOT.jar
@@ -79,24 +79,24 @@
Again, unlike Scala-JVM, these @code{.jar} files are not directly executable: the @code{.sjsir} files need further processing to turn into runnable Javascript. Instead, their sole purpose is to hold bundles of @code{.sjsir} files to be published and depended-upon: they can be @code{publishLocal}ed to be used by other projects on your computer, or @code{publishSigned}ed to @lnk("Maven Central", "http://search.maven.org/"), just like any Scala-JVM project.
@sect{The fastOptJS Command}
- @hl.bash
+ @hl.sh
> fastOptJS
@p
- @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.
+ @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.xml{<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.
@sect{The fullOptJS Command}
- @hl.bash
+ @hl.sh
> fullOptJS
@p
- @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.
+ @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.xml{<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.
@sect{The run Command}
- @hl.bash
+ @hl.sh
> run
@p
Here's something you haven't seen before: the @code{run} command gives you the ability to run a Scala.js program from the command line. This prints its output to standard output (i.e. the terminal). Like Scala-JVM, you need a @code{main} method to run to kick off your program. Unlike Scala-JVM, the main method is marked on an @hl.scala{object} which @hl.scala{extends scala.scalajs.js.JSApp}, e.g.
@@ -113,7 +113,7 @@
@p
Running @code{sbt run} with the above Scala.js code will print out
- @hl.bash
+ @hl.sh
Hello World!
In Scala.js, (1.0).toString is 1!
@@ -129,7 +129,7 @@
@p
You can enable a different stage, @code{FastOptStage} or @code{FullOptStage}, with the following sbt command:
- @hl.bash
+ @hl.sh
> set scalaJSStage in Global := FastOptStage
@p
@@ -145,7 +145,7 @@
Typically, the best way to get started is using Rhino, 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{Stages} to learn more about the other settings and why you would want to use them.
@sect{The test Command}
- @hl.bash
+ @hl.sh
> test
@p
diff --git a/book/src/main/scalatex/handson/GettingStarted.scalatex b/book/src/main/scalatex/handson/GettingStarted.scalatex
index 6243ace..e803fc1 100644
--- a/book/src/main/scalatex/handson/GettingStarted.scalatex
+++ b/book/src/main/scalatex/handson/GettingStarted.scalatex
@@ -21,7 +21,7 @@
@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
+@hl.sh
git clone https://github.com/lihaoyi/workbench-example-app
cd workbench-example-app
sbt ~fastOptJS
@@ -152,7 +152,7 @@
@hl.ref(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(){...}}
+ 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.js{function(){...}}
@sect{The Project Code}
@@ -185,10 +185,10 @@
@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{<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.xml{<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{example.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.
+ The @hl.scala{example.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.js{getElementById}) to it so it knows where to do its thing.
@p
@@ -201,7 +201,7 @@
@p
The last thing that we'll do with our toy application is to publish it. If you look in the @code{target/scala-2.11} folder, you'll see the output of everything we've done so far:
- @hl.bash
+ @hl.sh
target/scala-2.11
├── classes
│   ├── JS_DEPENDENCIES
@@ -226,14 +226,14 @@
@p
These two files can be extracted and published as-is: you can put them on @lnk("Github-Pages", "https://pages.github.com/"), @lnk("Amazon Web Services", "http://docs.aws.amazon.com/gettingstarted/latest/swh/website-hosting-intro.html"), or a hundred other places. However, one thing of note is the fact that the generated Javascript file is quite large:
- @hl.bash
+ @hl.sh
haoyi-mbp:temp haoyi$ du -h target/scala-2.11/example-fastopt.js
656K target/scala-2.11/example-fastopt.js
@p
656 Kilobytes for a hello world app! That is clearly too large. If you examine the contents of the file, you'll see that your code has been translated into something like this:
- @hl.javascript
+ @hl.js
var v1 = i;
if (((count$1.elem$1 % 3000) === 0)) {
ScalaJS.m.Lexample_ScalaJSExample$().example$ScalaJSExample$$clear$1__Lorg_scalajs_dom_CanvasRenderingContext2D__V(ctx$1)
@@ -247,20 +247,20 @@
var g = ((((255 - ScalaJS.as.Lexample_Point(p$1.elem$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 @sect.ref{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.js{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 @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
+ @hl.sh
haoyi-mbp:temp haoyi$ du -h target/scala-2.11/example-opt.js
104K target/scala-2.11/example-opt.js
@p
104 Kilobytes! Better. Not great, though! In general, Scala.js does not produce tiny executables, although the output size of the compiled executables is dropping all the time. If you look inside that file, you'll see all of the long identifiers have been replaced by short ones by the @lnk("Google Closure Compiler", "https://developers.google.com/closure/compiler/").
- @hl.javascript("""
+ @hl.js("""
y=fb(gb((new F).Ya(["rgb(",", ",", ",")"])),(new F).Ya([(255-c.l.Db|0)*y|0,c.l.Db*y|0,c.l.Eb]));a.fillStyle=y;a.fillRect(c.l.Db,c.l.Eb,1,1);w=1+w|0}}}(a,b,c,e),50)}Xa.prototype.main=function(a){Ya(a)};Xa.prototype.a=new x({$g:0},!1,"example.ScalaJSExample$",B,{$g:1,b:1});var hb=void 0;function bb(){hb||(hb=(new Xa).c());return hb}ba.example=ba.example||{};ba.example.ScalaJSExample=bb;function Da(){this.Pb=null}Da.prototype=new A;
""")
diff --git a/book/src/main/scalatex/handson/PublishingModules.scalatex b/book/src/main/scalatex/handson/PublishingModules.scalatex
index 61a21d5..395ead9 100644
--- a/book/src/main/scalatex/handson/PublishingModules.scalatex
+++ b/book/src/main/scalatex/handson/PublishingModules.scalatex
@@ -11,7 +11,7 @@
@p
As always, we will start with an example: in this case a toy library whose sole purpose in life is to take a series of timestamps (milliseconds UTC) and format them into a single, newline-delimited string. This is what the project layout looks like:
- @hl.bash
+ @hl.sh
$ tree
.
├── build.sbt
@@ -59,13 +59,13 @@
@hl.ref(simple/'library/'jvm/'src/'main/'scala/'simple/"Platform.scala")
@p
- In the @code{js/} version, we are using the Javascript @hl.javascript{Date} object to take the millis and do what we want. In the @code{jvm/} version, we instead use @hl.scala{java.text.SimpleDateFormat} with a custom formatter (The syntax is defined @lnk("here", "http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html")).
+ In the @code{js/} version, we are using the Javascript @hl.js{Date} object to take the millis and do what we want. In the @code{jvm/} version, we instead use @hl.scala{java.text.SimpleDateFormat} with a custom formatter (The syntax is defined @lnk("here", "http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html")).
@p
Again, you can put as much platform-specific logic in these files as you want, to account for differences in the available APIs. Maybe you want to use @lnk.dom.JSONparse for parsing JSON blobs in @code{js/}, but @lnk("Jackson", "http://jackson.codehaus.org/") or @lnk("GSON", "https://code.google.com/p/google-gson/") for parsing them in @code{jvm/}.
@sect{Running the Module}
- @hl.bash
+ @hl.sh
> ;libraryJS/test ;libraryJVM/test
[info] Compiling 1 Scala source to library/js/target/scala-2.10/test-classes...
[info] ---------------------------Results---------------------------
diff --git a/book/src/main/scalatex/handson/WebPage.scalatex b/book/src/main/scalatex/handson/WebPage.scalatex
index 13b5b20..cc232f2 100644
--- a/book/src/main/scalatex/handson/WebPage.scalatex
+++ b/book/src/main/scalatex/handson/WebPage.scalatex
@@ -11,24 +11,24 @@
@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 @hl.html{<div>} or @hl.html{<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.xml{<div>} or @hl.xml{<body>}. This approach works, as the following code snippet demonstrates:
@split
@more
@hl.ref(webpage/"HelloWorld0.scala")
@less
- @BookData.example(div, "webpage.HelloWorld0().main")
+ @book.BookData.example(div, "webpage.HelloWorld0().main")
@p
- Remember that we're now requiring a @hl.scala{html.Div} instead of a @hl.scala{html.Canvas} to be passed in when the Javascript calls @hl.javascript{webpage.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.
+ Remember that we're now requiring a @hl.scala{html.Div} instead of a @hl.scala{html.Canvas} to be passed in when the Javascript calls @hl.js{webpage.HelloWorld0().main(...)}. If you're coming to this point from the previous chapter, you'll need to update the on-page Javascript's @hl.js{document.getElementById} to pick a @hl.xml{<div>} rather than the @hl.xml{<canvas>} we were using in the previous chapter.
@p
This approach works, as the above example shows, but has a couple of disadvantages:
@ul
@li
- It is untyped: it is easy to accidentally mistype something, and result in malformed HTML. A typo such as @hl.html{<dvi>} would go un-noticed at build-time. Depending on where the typo happens, it could go un-noticed until the application is deployed, causing subtle bugs that only get resolved much later.
+ It is untyped: it is easy to accidentally mistype something, and result in malformed HTML. A typo such as @hl.xml{<dvi>} would go un-noticed at build-time. Depending on where the typo happens, it could go un-noticed until the application is deployed, causing subtle bugs that only get resolved much later.
@li
It is insecure: @lnk("Cross-site Scripting", "http://en.wikipedia.org/wiki/Cross-site_scripting") is a real thing, and it is easy to forget to escape the values you are putting into your HTML strings. Above they're constants like @hl.scala{"dog"}, but if they're user-defined, you may not notice there is a problem until something like @hl.scala{"<script>...</script>"} sneaks through and your users' accounts & data is compromised.
@@ -49,7 +49,7 @@
@hl.ref(webpage/"HelloWorld1.scala")
@less
- @BookData.example(div, "webpage.HelloWorld1().main")
+ @book.BookData.example(div, "webpage.HelloWorld1().main")
@p
Scalatags has some nice advantages over plain HTML: it's type-safe, so typos like @hl.scala{dvi} get caught at compile-time. It's also secure, such that you don't need to worry about script-tags in strings or similar. The @lnk("Scalatags Readme", "https://github.com/lihaoyi/scalatags#scalatags") elaborates on these points and other advantages. As you can see, it takes just 1 import at the top of the file to bring it in scope, and then you can use all of Scalatags' functionality.
@@ -63,7 +63,7 @@
@hl.ref(webpage/"Inputs.scala", "val box")
@less
- @BookData.example(div(height:="150px"), "webpage.Inputs().main")
+ @example(div(height:="150px"), "webpage.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.html.Input, @hl.scala{span.render} gives you a @lnk.dom.html.Span. You can then access the properties of these elements: adding callbacks, checking their value, anything you want.
@@ -81,7 +81,7 @@
@hl.ref(webpage/"Search0.scala", "val listings", "def")
@p
- Next, let's think about how we want to render these fruits. One natural way would be as a list, which in HTML is represented by a @hl.html{<ul>} with @hl.html{<li>}s inside of it if we wanted the list to be unordered. We'll make it a @hl.scala{def}, because we know up-front we're going to need to re-render this listing as the search query changes. Lastly, we know we want 1 list item for each fruit, but only if the fruit starts with the search query.
+ Next, let's think about how we want to render these fruits. One natural way would be as a list, which in HTML is represented by a @hl.xml{<ul>} with @hl.xml{<li>}s inside of it if we wanted the list to be unordered. We'll make it a @hl.scala{def}, because we know up-front we're going to need to re-render this listing as the search query changes. Lastly, we know we want 1 list item for each fruit, but only if the fruit starts with the search query.
@hl.ref(webpage/"Search0.scala", "def renderListings", "lazy val")
@@ -96,7 +96,7 @@
@hl.ref(webpage/"Search0.scala", "val output")
@less
- @BookData.example(div, "webpage.Search0().main")
+ @example(div, "webpage.Search0().main")
@p
And there you have it! A working search box. This is a relatively self-contained example: all the items its searching are available locally, no Ajax calls, and there's no fancy handling of the searched items. If we want to, for example, highlight the matched section of each fruit's name, we can modify the @hl.scala{def renderListings} call to do so:
@@ -106,7 +106,7 @@
@hl.ref(webpage/"Search1.scala", "def renderListings", "lazy val")
@less
- @BookData.example(div, "webpage.Search1().main")
+ @example(div, "webpage.Search1().main")
@p
Here, instead of sticking the name of the matched fruits directly into the @hl.scala{li}, we instead first split off the part which matches the query, and then highlght the first section yellow. Easy!
@@ -138,7 +138,7 @@
@hl.ref(webpage/"Weather0.scala", "val xhr")
@less
- @BookData.example(div(height:="400px"), "webpage.Weather0().main")
+ @example(div(height:="400px"), "webpage.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"), 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.
@@ -150,18 +150,18 @@
@hl.ref(wd/'examples/'demos/'src/'main/'resources/'webpage/"weather.js", "var xhr")
@less
- @BookData.example(div, "WeatherJs")
+ @example(div, "WeatherJs")
@p
The primary syntactic differences are:
@ul
@li
- @hl.scala{val}s for immutable data v.s. mutable @hl.javascript{var}s.
+ @hl.scala{val}s for immutable data v.s. mutable @hl.js{var}s.
@li
- @hl.scala("=>") v.s. @hl.javascript{function} to define the callback.
+ @hl.scala("=>") v.s. @hl.js{function} to define the callback.
@li
- Scalatags' @hl.scala{pre} v.s. @hl.javascript{document.createElement("pre")}
+ Scalatags' @hl.scala{pre} v.s. @hl.js{document.createElement("pre")}
@p
Overall, they're pretty close, which is a common theme in Scala.js: using Javascript APIs in Scala.js is often as seamless and easy as using them in Javascript itself, and it often looks almost identical.
@@ -186,7 +186,7 @@
@hl.ref(webpage/"Weather1.scala", "val url")
@less
- @BookData.example(div(height:="400px", overflow:="scroll"), "webpage.Weather1().main")
+ @example(div(height:="400px", overflow:="scroll"), "webpage.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
@@ -203,7 +203,7 @@
@hl.ref(webpage/"Weather2.scala", "Ajax.get")
@less
- @BookData.example(div(height:="400px"), "webpage.Weather2().main")
+ @example(div(height:="400px"), "webpage.Weather2().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.
@@ -216,7 +216,7 @@
@hl.ref(webpage/"Weather3.scala", "Ajax.get")
@less
- @BookData.example(div(height:="400px", overflow:="scroll"), "webpage.Weather3().main")
+ @example(div(height:="400px", overflow:="scroll"), "webpage.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.
@@ -246,7 +246,7 @@
@p
Here is the meat and potatoes of this program: every time it gets called with an array of weather-data, we iterate over the cities in that array. It then does a similar sort of data-extraction that we did earlier, putting the results into the @hl.scala{output} div we defined above, including highlighting.
- @BookData.example(div, "webpage.WeatherSearch().main")
+ @example(div, "webpage.WeatherSearch().main")
@p
And that's the working example! Try searching for cities like "Singapore" or "New York" or "San Francisco" and watch as the search narrows as you enter more characters into the text box. Note that the OpenWeatherMap API limits ambiguous searches to about a dozen results, so if a city doesn't turn up in a partial-search, try entering more characters to narrow it down.
diff --git a/book/src/main/scalatex/indepth/AdvancedTechniques.scalatex b/book/src/main/scalatex/indepth/AdvancedTechniques.scalatex
index 66b8b10..4460a1d 100644
--- a/book/src/main/scalatex/indepth/AdvancedTechniques.scalatex
+++ b/book/src/main/scalatex/indepth/AdvancedTechniques.scalatex
@@ -78,7 +78,7 @@
Scalatags requires that anything you want to embed in a Scalatags fragment be implicitly convertible to @hl.scala{Frag}; here we are providing one for any Scala.Rx @hl.scala{Rx[T]}s, as long as the @hl.scala{T} provided is itself convertible to a @hl.scala{Frag}. We call @hl.scala{r().render} to extract the "current" value of the @hl.scala{Rx}, and then set up an @hl.scala{Obs} that watches the @hl.scala{Rx}, replacing the previous value with the current one every time its value changes.
@p
- Now that the set-up is out of the way, let's consider a simple HTML widget that lets you enter text in a @hl.html{<textarea>}, and keeps track of the number of words, characters, and counts how long each word is.
+ Now that the set-up is out of the way, let's consider a simple HTML widget that lets you enter text in a @hl.xml{<textarea>}, and keeps track of the number of words, characters, and counts how long each word is.
@split
@more
diff --git a/book/src/main/scalatex/indepth/CompilationPipeline.scalatex b/book/src/main/scalatex/indepth/CompilationPipeline.scalatex
index fe2adb9..0c3d927 100644
--- a/book/src/main/scalatex/indepth/CompilationPipeline.scalatex
+++ b/book/src/main/scalatex/indepth/CompilationPipeline.scalatex
@@ -96,7 +96,7 @@
@sect{Fast Optimization}
@p
Without optimizations, the actual JavaScript code emitted for the above snippet would look like this:
- @hl.javascript
+ @hl.js
ScalaJS.c.Lexample_ScalaJSExample$.prototype.main__V = (function() {
var x = 0;
while ((x < 999)) {
@@ -140,7 +140,7 @@
@p
Applying these optimizations on our examples results in the following JavaScript code instead, which is what you typically execute in fastOpt stage:
- @hl.javascript
+ @hl.js
ScalaJS.c.Lexample_ScalaJSExample$.prototype.main__V = (function() {
var x = 0;
while ((x < 999)) {
@@ -163,7 +163,7 @@
While the input for this phase is the aggregate @code{.sjsir} files from your project and all your dependencies, the output is executable Javascript. This phase usually runs in less than a second, outputs a Javascript blob in the 400kb-1mb range, and is suitable for repeated use during development. This corresponds to the @code{fastOptJS} command in SBT.
@sect{Full Optimization}
- @hl.javascript
+ @hl.js
Fd.prototype.main = function() {
for(var a = 0;999 > a;) {
var b = (new D).j("2");
@@ -179,7 +179,7 @@
The @lnk("Google Closure Compiler", "https://developers.google.com/closure/compiler/") (GCC) is a set of tools that work with Javascript. It has multiple @lnk("levels of optimization", "https://developers.google.com/closure/compiler/docs/compilation_levels"), doing everything from basic whitespace-removal to heavy optimization. It is an old, relatively mature project that is relied on both inside and outside Google to optimize the delivery of Javascript to the browser.
@p
- Scala.js uses GCC in its most aggressive mode: @lnk("Advanced Optimization", "https://developers.google.com/closure/compiler/docs/api-tutorial3"). GCC spits out a compressed, minified version of the Javascript (above) that @sect.ref{Fast Optimization} spits out: e.g. in the example above, all identifiers have been renamed to short strings, the @hl.javascript{while}-loop has been replaced by a @hl.javascript{for}-loop, and the @hl.scala{println} function has been inlined.
+ Scala.js uses GCC in its most aggressive mode: @lnk("Advanced Optimization", "https://developers.google.com/closure/compiler/docs/api-tutorial3"). GCC spits out a compressed, minified version of the Javascript (above) that @sect.ref{Fast Optimization} spits out: e.g. in the example above, all identifiers have been renamed to short strings, the @hl.js{while}-loop has been replaced by a @hl.js{for}-loop, and the @hl.scala{println} function has been inlined.
@p
As described in the linked documentation, GCC performs optimizations such as:
diff --git a/book/src/main/scalatex/indepth/DesignSpace.scalatex b/book/src/main/scalatex/indepth/DesignSpace.scalatex
index 6eec6fd..b310c5c 100644
--- a/book/src/main/scalatex/indepth/DesignSpace.scalatex
+++ b/book/src/main/scalatex/indepth/DesignSpace.scalatex
@@ -134,19 +134,19 @@
@hl.scala
/*JVM*/
15 / 4 // 3
- @hl.javascript
+ @hl.js
/*JS*/
15 / 4 // 3.25
@p
- On the JVM, integer division is a primitive, and dividing @hl.scala{15 / 4} gives @hl.scala{3}. However, in Javascript, it gives @hl.javascript{3.25}, since all numbers of double-precision floating points.
+ On the JVM, integer division is a primitive, and dividing @hl.scala{15 / 4} gives @hl.scala{3}. However, in Javascript, it gives @hl.js{3.25}, since all numbers of double-precision floating points.
@p
- Scala.js works around this in the general case by adding a @hl.javascript{| 0} to the translation, e.g.
+ Scala.js works around this in the general case by adding a @hl.js{| 0} to the translation, e.g.
@hl.scala
/*JVM*/
15 / 4 // 3
- @hl.javascript
+ @hl.js
/*JS*/
(15 / 4) | 0 // 3
@@ -156,19 +156,19 @@
@hl.scala
/*JVM*/
15 / 0 // ArithmeticException
- @hl.javascript
+ @hl.js
/*JS*/
15 / 0 // Infinity
(15 / 0) | 0 // 0
@p
- On the JVM, the JVM is kind enough to throw an exception for you. However, in Javascript, the integer simply wraps around to @hl.javascript{Infinity}, which then gets truncated down to zero.
+ On the JVM, the JVM is kind enough to throw an exception for you. However, in Javascript, the integer simply wraps around to @hl.js{Infinity}, which then gets truncated down to zero.
@p
So that's the current behavior of integers in Scala.js. One may ask: can we fix it? And the answer is, we can:
@hl.scala
/*JVM*/
1 / 0 // ArithmeticException
- @hl.javascript
+ @hl.js
/*JS*/
function intDivide(x, y){
var z = x / y
@@ -207,7 +207,7 @@
@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.
+ 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.js{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
diff --git a/book/src/main/scalatex/indepth/JavaAPIs.scalatex b/book/src/main/scalatex/indepth/JavaAPIs.scalatex
index 51ac71f..eb09ff9 100644
--- a/book/src/main/scalatex/indepth/JavaAPIs.scalatex
+++ b/book/src/main/scalatex/indepth/JavaAPIs.scalatex
@@ -8,7 +8,7 @@
@sect{Available Java APIs}
@ul
- @for(data <- BookData.javaAPIs)
+ @for(data <- book.BookData.javaAPIs)
@li
@a(data._1, href:=data._2)
diff --git a/build.sbt b/build.sbt
index c8de546..a28e67d 100644
--- a/build.sbt
+++ b/build.sbt
@@ -19,10 +19,10 @@ lazy val book = Project(
base = file("book")
).settings(sharedSettings ++ scalatex.SbtPlugin.projectSettings:_*).settings(
libraryDependencies ++= Seq(
- "com.lihaoyi" %% "scalatags" % "0.4.6",
- "com.lihaoyi" %%% "upickle" % "0.2.7",
- "com.lihaoyi" %% "scalatex-site" % "0.1.1",
- "com.lihaoyi" %% "ammonite" % "0.1.0"
+ "com.lihaoyi" %% "scalatex-site" % "0.2.1",
+ "com.lihaoyi" %% "scalatags" % "0.5.1",
+ "com.lihaoyi" %% "upickle" % "0.2.7",
+ "com.lihaoyi" %% "ammonite-ops" % "0.2.4"
),
(resources in Compile) += (fullOptJS in (demos, Compile)).value.data,
diff --git a/examples/demos/src/main/scala/scrollmenu/Controller.scala b/examples/demos/src/main/scala/scrollmenu/Controller.scala
index 25af158..bdc2afc 100644
--- a/examples/demos/src/main/scala/scrollmenu/Controller.scala
+++ b/examples/demos/src/main/scala/scrollmenu/Controller.scala
@@ -74,7 +74,7 @@ object Controller{
menuLink.classList.toggle("active")
}
- dom.document.body.onscroll = (e: dom.UIEvent) => updateScroll()
+ dom.addEventListener("scroll", (e: dom.UIEvent) => updateScroll())
updateScroll()
}
diff --git a/project/build.sbt b/project/build.sbt
index a4c9041..5ef6e33 100644
--- a/project/build.sbt
+++ b/project/build.sbt
@@ -4,4 +4,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.7.4")
libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit" % "3.5.1.201410131835-r"
-addSbtPlugin("com.lihaoyi" % "scalatex-sbt-plugin" % "0.1.1")
+addSbtPlugin("com.lihaoyi" % "scalatex-sbt-plugin" % "0.2.1")