summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHaoyi Li <haoyi@haoyi-mbp.corp.dropbox.com>2014-11-26 01:43:13 -0800
committerHaoyi Li <haoyi@haoyi-mbp.corp.dropbox.com>2014-11-26 01:43:13 -0800
commit0489a0d6c8d3beceeedb8c2f6eaad7b9387073c0 (patch)
tree95efbc083dee7d9ce8211d44d3f98c4a34c26ad4
parent88595a41e3ec13c1a516e847fe3d0b279facf3fc (diff)
downloadhands-on-scala-js-0489a0d6c8d3beceeedb8c2f6eaad7b9387073c0.tar.gz
hands-on-scala-js-0489a0d6c8d3beceeedb8c2f6eaad7b9387073c0.tar.bz2
hands-on-scala-js-0489a0d6c8d3beceeedb8c2f6eaad7b9387073c0.zip
one-button build
-rw-r--r--book/src/main/scala/book/BookData.scala20
-rw-r--r--book/src/main/scala/book/Utils.scala7
-rw-r--r--book/src/main/scalatex/book/handson/ClientServer.scalatex1
-rw-r--r--book/src/main/scalatex/book/handson/CommandLine.scalatex1
-rw-r--r--book/src/main/scalatex/book/handson/GettingStarted.scalatex19
-rw-r--r--book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex1
-rw-r--r--book/src/main/scalatex/book/indepth/DesignSpace.scalatex2
-rw-r--r--book/src/main/scalatex/book/indepth/JavaAPIs.scalatex4
-rw-r--r--build.sbt28
-rw-r--r--project/build.properties2
-rw-r--r--project/build.sbt2
-rw-r--r--readme.md10
12 files changed, 76 insertions, 21 deletions
diff --git a/book/src/main/scala/book/BookData.scala b/book/src/main/scala/book/BookData.scala
index af8d2cb..ca6efff 100644
--- a/book/src/main/scala/book/BookData.scala
+++ b/book/src/main/scala/book/BookData.scala
@@ -1,9 +1,14 @@
package book
+import java.io.File
+
import acyclic.file
import scalatags.Text.TypedTag
import scalatags.Text.all._
object BookData {
+ val cloneRoot = System.getProperty("clone.root") + "/"
+
+
lazy val javaAPIs = {
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
@@ -12,12 +17,12 @@ object BookData {
}
val roots = Seq(
- "examples/scala-js/javalanglib/src/main/scala",
- "examples/scala-js/javalib/src/main/scala"
+ "scala-js/javalanglib/src/main/scala",
+ "scala-js/javalib/src/main/scala"
)
for{
root <- roots
- file <- recursiveListFiles(new File(root))
+ file <- recursiveListFiles(new File(cloneRoot + root))
if file != null
if file.isFile
} yield{
@@ -42,4 +47,13 @@ object BookData {
def more = div(cls:="pure-u-1 pure-u-md-13-24")
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 Highlighter(
+ Seq(
+ s"$cloneRoot/scala-js" -> "https://github.com/scala-js/scala-js",
+ s"$cloneRoot/workbench-example-app" -> "https://github.com/lihaoyi/workbench-example-app",
+ "" -> "https://github.com/lihaoyi/hands-on-scala-js"
+ )
+ )
}
diff --git a/book/src/main/scala/book/Utils.scala b/book/src/main/scala/book/Utils.scala
index b59fb59..2c861d0 100644
--- a/book/src/main/scala/book/Utils.scala
+++ b/book/src/main/scala/book/Utils.scala
@@ -123,7 +123,7 @@ object lnk{
}
}
-object hl{
+class Highlighter(mappings: Seq[(String, String)]){
def highlight(snippet: Seq[String], lang: String) = {
val string = snippet.mkString
val lines = string.split("\n", -1)
@@ -147,11 +147,6 @@ object hl{
def diff(code: String*) = highlight(code, "diff")
def html(code: String*) = highlight(code, "xml")
- val mappings = Seq(
- "examples/scala-js" -> "https://github.com/scala-js/scala-js",
- "examples/workbench-example-app" -> "https://github.com/lihaoyi/workbench-example-app",
- "" -> "https://github.com/lihaoyi/hands-on-scala-js"
- )
def ref(filepath: String, start: String = "", end: String = "\n") = {
val lang = filepath.split('.').last match {
diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex
index 0a41dae..3a952dc 100644
--- a/book/src/main/scalatex/book/handson/ClientServer.scalatex
+++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex
@@ -1,3 +1,4 @@
+@import BookData._
@p
Historically, sharing code across client & server has been a holy-grail for web development. There are many things which have made it hard in the past:
diff --git a/book/src/main/scalatex/book/handson/CommandLine.scalatex b/book/src/main/scalatex/book/handson/CommandLine.scalatex
index 0663378..439804c 100644
--- a/book/src/main/scalatex/book/handson/CommandLine.scalatex
+++ b/book/src/main/scalatex/book/handson/CommandLine.scalatex
@@ -1,3 +1,4 @@
+@import BookData._
@p
We've by this point done a bunch of work in the browser: we've made a small game that runs in the web browser on the HTML5 canvas, and we've made a number of small web-apps that interact with HTML and 3rd party web-services. However, there's a whole other side to the Scala.js ecosystem: the command line interace, or CLI.
diff --git a/book/src/main/scalatex/book/handson/GettingStarted.scalatex b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
index 7c06a80..571876f 100644
--- a/book/src/main/scalatex/book/handson/GettingStarted.scalatex
+++ b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
@@ -1,3 +1,4 @@
+@import BookData._
@p
To get started with Scala.js, you will need to prepare a few things:
@@ -98,17 +99,17 @@
@p
We've downloaded, compiled, ran, and made changes to our first Scala.js application. Let's now take a closer look at the code that we just ran:
- @hl.ref("examples/workbench-example-app/src/main/scala/example/ScalaJSExample.scala")
+ @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala")
@p
It's a good chunk of code, though not a huge amount. To someone who didn't know about Scala.js, they would just think it's normal Scala, albeit with this unusual @hl.scala{dom} library and a few weird annotations. Let's pick it apart starting from the top:
- @hl.ref("examples/workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "case class Point", "@JSExport")
+ @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "case class Point", "@JSExport")
@p
Here we are defining a @hl.scala{Point} case class which represents a X/Y position, with some basic operators defined on it. This is done mostly for convenience later on, when we want to manipulate these two-dimensional points. Scala.js is Scala, and supports the entirety of the Scala language. @hl.scala{Point} here behaves identically as it would if you had run Scala on the JVM.
- @hl.ref("examples/workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "@JSExport", "val ctx")
+ @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "@JSExport", "val ctx")
@p
This @hl.scala("@JSExport") annotation is used to tell Scala.js that you want this method to be visible and callable from Javascript. By default, Scala.js does @sect.ref("Fast Optimization", "dead code elimination") and removes any methods or classes which are not used. This is done to keep the compiled executables a reasonable size, since most projects use only a small fraction of e.g. the standard library. @hl.scala("@JSExport") is used to tell Scala.js that the @hl.scala{ScalaJSExample} object and its @hl.scala{def main} method are entry points to the program. Even if they aren't called anywhere internally, they are called externally by Javascript that the Scala.js compiler is not aware of, and should not be removed. In this case, we are going to call this method from Javascript to start the Scala.js program.
@@ -116,7 +117,7 @@
@p
Apart from this annotation, @hl.scala{ScalaJSExample} is just a normal Scala @hl.scala{object}, and behaves like one in every way. Note that the main-method in this case takes a @lnk.dom.HTMLCanvasElement: your exported methods can have any signature, with arbitrary arity or types for parameters or the return value. This is in contrast to the main method on the JVM which always takes an @hl.scala{Array[String]} and returns @hl.scala{Unit}. In fact, there's nothing special about this method at all! It's like any other exported method, we just happen to attribute it the "main" entry point. It is entirely possible to define multiple exported classes and methods, and build a "library" using Scala.js of methods that are intended for external Javascript to use.
- @hl.ref("examples/workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "val ctx", "var count")
+ @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "val ctx", "var count")
@p
Here we are retrieving a handle to the canvas we will draw on using @hl.scala{document.getElementById}, and from it we can get a @lnk.dom.CanvasRenderingContext2D which we actually use to draw on it.
@@ -127,7 +128,7 @@
@p
We need to perform the @hl.scala{asInstanceOf} call because depending on what you pass to @hl.scala{getElementById} and @hl.scala{getContext}, you could be returned elements and contexts of different types. Hence we need to tell the compiler explicitly that we're expecting a @lnk.dom.HTMLCanvasElement and @lnk.dom.CanvasRenderingContext2D back from these methods for the strings we passed in.
- @hl.ref("examples/workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "def run", "dom.setInterval")
+ @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "def run", "dom.setInterval")
@p
This is the part of the Scala.js program which does the real work. It runs 10 iterations of a @lnk("small algorithm", "http://en.wikipedia.org/wiki/Sierpinski_triangle#Chaos_game") that generates a Sierpinski Triangle point-by-point. The steps, as described by the linked article, are roughly:
@@ -145,7 +146,7 @@
@p
In this example, the triangle is hard-coded to be 255 pixels high by 255 pixels wide, and some math is done to pick a color for each dot which will give the triangle a pretty gradient.
- @hl.ref("examples/workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "dom.setInterval")
+ @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "dom.setInterval")
@p
Now this is the call that actually does the useful work. All this method does is call @hl.scala{dom.setInterval}, which tells the browser to run the @hl.scala{run} method every 50 milliseconds. As mentioned earlier, the @hl.scala{dom.*} methods are simply facades to their native Javascript equivalents, and @hl.scala{dom.setInterval} is @lnk("no different", "https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers.setInterval"). Note how you can pass a Scala lambda to @hl.scala{setInterval} to have it called by the browser, where in Javascript you'd need to pass a Javascript @hl.javascript{function(){...}}
@@ -156,7 +157,7 @@
We've already taken a look at the application code for a simple, self-contained Scala.js application, but this application is not @i{entirely} self contained. It's wrapped in a small SBT project that sets up the necessary dependencies and infrastructure for this application to work.
@sect{project/build.sbt}
- @hl.ref("examples/workbench-example-app/project/build.sbt")
+ @hl.ref(cloneRoot + "workbench-example-app/project/build.sbt")
@p
This is the list of SBT plugins used by this small example application. There are two of them: the Scala.js plugin (which contains the Scala.js compiler and other things, e.g. tasks such as @code{fastOptJS}) and the @lnk("Workbench", "https://github.com/lihaoyi/workbench") plugin, which is used to provide the auto-reload-on-change behavior and the forwarding of SBT logspam to the browser console.
@@ -166,7 +167,7 @@
@sect{build.sbt}
- @hl.ref("examples/workbench-example-app/build.sbt")
+ @hl.ref(cloneRoot + "workbench-example-app/build.sbt")
@p
The @code{build.sbt} project file for this application is similarly unremarkable: It includes the settings for the two SBT plugins we saw earlier, as well as boilerplate @hl.scala{name}/@hl.scala{version}/@hl.scala{scalaVersion} values common to all projects.
@@ -178,7 +179,7 @@
Lastly, we have two Workbench related settings: @hl.scala{bootSnippet} basically tells Workbench how to restart your application when a new compilation run finishes, and @hl.scala{updateBrowsers} actually tells it to perform this application-restarting.
@sect{src/main/resources/index-dev.html}
- @hl.ref("examples/workbench-example-app/src/main/resources/index-dev.html")
+ @hl.ref(cloneRoot + "workbench-example-app/src/main/resources/index-dev.html")
@p
This is the HTML page which our toy app lives in, and the same page that we have so far been using to view the app in the browser. To anyone who has used HTML, most of it is probably familiar. Things of note are the @hl.html{<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.
diff --git a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
index 1718f5e..cb55bf9 100644
--- a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
+++ b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
@@ -1,3 +1,4 @@
+@import BookData._
@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.
diff --git a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
index a1e807f..f167ea6 100644
--- a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
+++ b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
@@ -1,3 +1,5 @@
+@import BookData._
+
@p
Scala.js is a relatively large project, and is the result of both an enormous amount of hard work as well as a number of decisions that craft what it's like to program in Scala.js today. Many of these decisions result in marked differences from the behavior of the same code running on the JVM. This chapter explores the reasoning and rationale behind these decisions.
diff --git a/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex b/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
index 9b526a4..685fdfd 100644
--- a/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
+++ b/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
@@ -1,3 +1,5 @@
+@import BookData._
+
@p
Below is a list of classes from the Java Standard Library that are available from Scala.js. In general, much of @hl.scala{java.lang}, and parts of @hl.scala{java.io}, @hl.scala{java.util} and @hl.scala{java.net} have been ported over. This means that all these classes are available for use in Scala.js applications despite being part of the Java standard library.
@p
@@ -37,7 +39,7 @@
@p
And other similar APIs will either need to be rewritten to not-use them. For example, @hl.scala{AtomicXXXs} can be written without threading/unsafe APIs because Javascript is single-threaded, making the implementation for e.g. an @hl.scala{AtomicBoolean} pretty trivial:
- @hl.ref("examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala")
+ @hl.ref(cloneRoot + "/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala")
@p
Others can't be ported at all (e.g. @code{java.io.File}) simply because the API capabilities they provide (blocking reads & writes to files) do not exist in the Javascript runtime.
diff --git a/build.sbt b/build.sbt
index 8df7b50..441d8d5 100644
--- a/build.sbt
+++ b/build.sbt
@@ -2,6 +2,8 @@
import scala.scalajs.sbtplugin.ScalaJSPlugin._
import ScalaJSKeys._
+val cloneRepos = taskKey[Unit]("Clone stuff from github")
+
lazy val scalaParser = project.in(file("scalaParser")).settings(
scalaVersion := "2.11.4",
libraryDependencies ++= Seq(
@@ -76,7 +78,31 @@ lazy val book = Project(
},
libraryDependencies += "com.lihaoyi" %% "acyclic" % "0.1.2" % "provided",
autoCompilerPlugins := true,
- addCompilerPlugin("com.lihaoyi" %% "acyclic" % "0.1.2")
+ addCompilerPlugin("com.lihaoyi" %% "acyclic" % "0.1.2"),
+ cloneRepos := {
+ val localPath = crossTarget.value / "clones"
+ if (!localPath.isDirectory){
+ val paths = Seq(
+ "scala-js" -> "scala-js",
+ "lihaoyi" -> "workbench-example-app"
+ )
+ localPath.delete()
+ for ((user, repo) <- paths){
+ println(s"Cloning $repo...")
+ org.eclipse.jgit.api.Git.cloneRepository()
+ .setURI(s"https://github.com/$user/$repo")
+ .setDirectory(localPath / repo)
+ .call()
+ }
+ println("Done Cloning")
+ }else{
+ println("Already Cloned")
+ }
+ },
+ (run in Compile) <<= (run in Compile).dependsOn(cloneRepos),
+ initialize := {
+ System.setProperty("clone.root", crossTarget.value.getAbsolutePath + "/clones")
+ }
)
lazy val demos = project.in(file("examples/demos"))
diff --git a/project/build.properties b/project/build.properties
index be6c454..748703f 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version=0.13.5
+sbt.version=0.13.7
diff --git a/project/build.sbt b/project/build.sbt
index 1fbce95..02ad299 100644
--- a/project/build.sbt
+++ b/project/build.sbt
@@ -1,3 +1,5 @@
addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.5")
addSbtPlugin("com.lihaoyi" % "utest-js-plugin" % "0.2.4")
+
+libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit" % "3.5.1.201410131835-r" \ No newline at end of file
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..02f2276
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,10 @@
+Hands-on Scala.js
+=================
+
+Source code for [lihaoyi.github.io/hands-on-scala-js](lihaoyi.github.io/hands-on-scala-js)
+
+To build,
+
+```
+sbt book/run
+``` \ No newline at end of file