summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-10-08 21:32:56 -0700
committerLi Haoyi <haoyi@dropbox.com>2014-10-08 21:32:56 -0700
commitb5b95c7d83b376c74a43848715228dfd3a415354 (patch)
tree56d3755bbef582127601160d2a43541f57fdd5bd
parenta57376f40b539260f1d13d3278f10e16a4a2fbe1 (diff)
downloadworkbench-b5b95c7d83b376c74a43848715228dfd3a415354.tar.gz
workbench-b5b95c7d83b376c74a43848715228dfd3a415354.tar.bz2
workbench-b5b95c7d83b376c74a43848715228dfd3a415354.zip
0.2.2
-rw-r--r--Error.pngbin0 -> 53881 bytes
-rw-r--r--build.sbt2
-rw-r--r--example/src/main/scala/example/ScalaJSExample.scala21
-rw-r--r--readme.md60
-rw-r--r--src/main/scala/workbench/Plugin.scala11
-rw-r--r--src/main/scala/workbench/Server.scala5
6 files changed, 83 insertions, 16 deletions
diff --git a/Error.png b/Error.png
new file mode 100644
index 0000000..6faa17a
--- /dev/null
+++ b/Error.png
Binary files differ
diff --git a/build.sbt b/build.sbt
index 677ded5..c963881 100644
--- a/build.sbt
+++ b/build.sbt
@@ -9,7 +9,7 @@ val defaultSettings = Seq(
lazy val root = project.in(file(".")).settings(defaultSettings:_*).settings(
name := "workbench",
- version := "0.2.1",
+ version := "0.2.2",
organization := "com.lihaoyi",
scalaVersion := "2.10.4",
sbtPlugin := true,
diff --git a/example/src/main/scala/example/ScalaJSExample.scala b/example/src/main/scala/example/ScalaJSExample.scala
index 9df7b18..40848a8 100644
--- a/example/src/main/scala/example/ScalaJSExample.scala
+++ b/example/src/main/scala/example/ScalaJSExample.scala
@@ -12,16 +12,17 @@ case class Point(x: Int, y: Int){
// mkdir ~/.sbt/0.13/plugins/target/scala-2.10/sbt-0.13/classes
@JSExport
object ScalaJSExample {
- val ctx = dom.document
- .getElementById("canvas")
- .asInstanceOf[dom.HTMLCanvasElement]
- .getContext("2d")
- .asInstanceOf[dom.CanvasRenderingContext2D]
+ val ctx =
+ dom.document
+ .getElementById("canvas")
+ .asInstanceOf[dom.HTMLCanvasElement]
+ .getContext("2d")
+ .asInstanceOf[dom.CanvasRenderingContext2D]
var p = Point(128, 128)
- def color = "black"
+ def color = "grey"
- var enemies = List.fill(10)(Point(util.Random.nextInt(255), util.Random.nextInt(255)))
+ var enemiess = List.fill(10)(Point(util.Random.nextInt(255), util.Random.nextInt(255)))
def clear() = {
ctx.fillStyle = color
ctx.fillRect(0, 0, 255, 255)
@@ -29,10 +30,10 @@ object ScalaJSExample {
def run = {
clear()
- ctx.fillStyle = "cyan"
+ ctx.fillStyle = "yellow"
p = Point(p.x, (p.y + 2) % 255)
ctx.fillRect(p.x, p.y, 5, 20)
- enemies = for (enemy <- enemies) yield {
+ enemiess = for (enemy <- enemiess) yield {
ctx.fillStyle = "red"
ctx.fillRect(enemy.x, enemy.y, 10, 10)
Point((enemy.x + 1) % 255, (enemy.y + 1) % 255)
@@ -40,8 +41,6 @@ object ScalaJSExample {
}
@JSExport
def main(): Unit = {
- dom.console.log("main")
-
dom.setInterval(() => run, 10)
}
}
diff --git a/readme.md b/readme.md
index 2ad1f66..21e0463 100644
--- a/readme.md
+++ b/readme.md
@@ -20,7 +20,7 @@ resolvers += "spray repo" at "http://repo.spray.io"
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
-addSbtPlugin("com.lihaoyi" % "workbench" % "0.2.1")
+addSbtPlugin("com.lihaoyi" % "workbench" % "0.2.2")
```
- Add to your `build.sbt`
```scala
@@ -62,7 +62,7 @@ This will to make any client browsers refresh every time `packageJS` completes,
updateBrowsers <<= updateBrowsers.triggeredBy(packageJS in Compile)
```
-This will attempt to perform an update without refreshing the page every time `packageJS` completes, which is much faster than a full page refresh since the browser doesn't need to parse/exec the huge blob of `extdeps.js`. This involves:
+This will attempt to perform an update without refreshing the page every time `fastOptJS` completes. This involves:
- Returning the state of `document.body` to the initial state before any javascript was run
- Stripping all event listeners from things within body
@@ -79,6 +79,41 @@ Nonetheless, for the bulk of javascript libraries these limitations are acceptab
You can force the clean-up-and-reboot to happen from the browser via the shortcut Ctrl-Alt-Shift-Enter if you simply wish to reset the browser to a clean state.
+#### spliceBrowsers
+
+```scala
+ScalaJSKeys.inliningMode := scala.scalajs.sbtplugin.InliningMode.Off
+
+spliceBrowsers <<= spliceBrowsers.triggeredBy(ScalaJSKeys.fastOptJS in Compile)
+```
+
+This is an experimental feature that aims to perform an update to the code running in the browser *without losing the state of the running code*! Thus you can make changes to the code and have them immediately appear in the program, without having to restart and lose the current state of the application. See [this video](https://vimeo.com/105852957) for a demo of it in action.
+
+This live splicing is not doable in the general case, but only for some subset of changes:
+
+- Changes inside method bodies
+- Adding new `def`s and `lazy val`s to classes/objects
+- Creating entirely new classes/objects
+
+This means that there are many changes that `spliceBrowsers` does not support, such as.
+
+- Adding new `val`s and `var`s to classes/objects
+- Modifying inheritance hierarchies
+- Changing the type of an existing `val`/`var`/`lazy val`
+- Renaming classes
+
+And many more. If the change is something that Workbench does not support, you'll see errors in the browser console:
+
+![Example](https://github.com/lihaoyi/scala-js-workbench/blob/master/Error.png?raw=true)
+
+And you'll need to refresh the page.
+
+Note that you have to turn off the Scala.js inliner (as shown in the above SBT snippet) in order to have this work. The inliner performs inlinings across class and method boundaries that makes it hard to predict whether or not the live-splicer will work.
+
+Lastly, note that `spliceBrowsers` does not retroactively modify the state of the application as if the changes in the code had always been present. For example, values set by the constructor in instances of a class will remain as-is even if you modify the class constructor; only new instances will be affected by the modified constructor.
+
+In general, it is entirely possible to get into weird/invalid states due to this live-splicing, and the general solution is simply to refresh the page.
+
-------
With this done, you should be receiving the SBT logspam (compilation, warnings, errors) in your browse console, and the page should be automatically refreshing/updating when the application gets recompiled. If you have problems setting this up, try starting from the [example app](https://github.com/lihaoyi/workbench-example-app) and working from there.
@@ -98,6 +133,27 @@ To make changes to workbench, modify the workbench source code and stop/re-run `
Pull requests welcome!
+Change Log
+----------
+
+##0.2.2
+
+- First implementation of `spliceBrowsers`
+
+##0.2.1
+
+- Added missing resolver `http://dl.bintray.com/non/maven`
+
+##0.2.0
+
+- First implementation of workbench client in Scala.js
+
+##0.1.5
+
+- Properly kill the spray server on plugin unload, `sbt reload` now works
+- Swap out `play-json` with `upickle`
+- (Internally) separate Spray server code with SBT madness
+
License
-------
The MIT License (MIT)
diff --git a/src/main/scala/workbench/Plugin.scala b/src/main/scala/workbench/Plugin.scala
index 85b8f39..3a1b175 100644
--- a/src/main/scala/workbench/Plugin.scala
+++ b/src/main/scala/workbench/Plugin.scala
@@ -80,7 +80,16 @@ object Plugin extends sbt.Plugin {
var s = IO.read(new sbt.File(path.drop(prefix.length)))
s = s.replace("\nvar ScalaJS = ", "\nvar ScalaJS = ScalaJS || ")
- s = s.replaceAll("\n(ScalaJS\\.c\\.[a-zA-Z_$0-9]+\\.prototype) = ", "/*X*/\n$1 = $1 || ")
+ s = s.replaceAll(
+ "\n(ScalaJS\\.c\\.[a-zA-Z_$0-9]+\\.prototype) = (.*?\n)",
+ """
+ |$1 = $1 || {}
+ |(function(){
+ | var newProto = $2
+ | for (var attrname in newProto) { $1[attrname] = newProto[attrname]; }
+ |})()
+ |""".stripMargin
+ )
for(char <- Seq("d", "c", "h", "i", "n", "m")){
s = s.replaceAll("\n(ScalaJS\\." + char + "\\.[a-zA-Z_$0-9]+) = ", "\n$1 = $1 || ")
}
diff --git a/src/main/scala/workbench/Server.scala b/src/main/scala/workbench/Server.scala
index 8d05037..3ffe288 100644
--- a/src/main/scala/workbench/Server.scala
+++ b/src/main/scala/workbench/Server.scala
@@ -56,15 +56,18 @@ class Server(url: String, port: Int, bootSnippet: String) extends SimpleRoutingA
// Even if there's someone already waiting,
// a new actor waiting replaces the old one
waitingActor = Some(a)
- case (a: ActorRef, None, msgs) =>
+ case (a: ActorRef, None, msgs) =>
respond(a, upickle.json.write(Js.Arr(msgs:_*)))
queuedMessages = Nil
+
case (msg: Js.Arr, None, msgs) =>
queuedMessages = msg :: msgs
+
case (msg: Js.Arr, Some(a), Nil) =>
respond(a, upickle.json.write(Js.Arr(msg)))
waitingActor = None
+
case (Clear, waiting, Nil) =>
waiting.foreach(respond(_, upickle.json.write(Js.Arr())))
waitingActor = None