summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlihaoyi <haoyi.sg@gmail.com>2013-11-17 23:51:23 -0800
committerlihaoyi <haoyi.sg@gmail.com>2013-11-17 23:51:23 -0800
commit1d659fdf8f3d0670076bdc03f54309c6e48df3c2 (patch)
treedf560d93ea177ae8100d62dad0bab0d65aec1082
parenta335846855c92dd650b678661a2325a42149c776 (diff)
downloadworkbench-1d659fdf8f3d0670076bdc03f54309c6e48df3c2.tar.gz
workbench-1d659fdf8f3d0670076bdc03f54309c6e48df3c2.tar.bz2
workbench-1d659fdf8f3d0670076bdc03f54309c6e48df3c2.zip
Added `updateBrowsers` command
-rw-r--r--package.scala36
-rw-r--r--readme.md23
-rw-r--r--workbench_template.ts30
3 files changed, 75 insertions, 14 deletions
diff --git a/package.scala b/package.scala
index 6bc58b3..0e0e7be 100644
--- a/package.scala
+++ b/package.scala
@@ -22,11 +22,12 @@ import spray.can.server.websockets.model.OpCode.Text
package object workbench extends sbt.Plugin {
val refreshBrowsers = taskKey[Unit]("Sends a message to all connected web pages asking them to refresh the page")
+ val updateBrowsers = taskKey[Unit]("partially resets some of the stuff in the browser")
val generateClient = taskKey[File]("generates a .js file that can be embedded in your web page")
val localUrl = settingKey[(String, Int)]("localUrl")
val server = settingKey[ActorRef]("local websocket server")
val fileName = settingKey[String]("name of the generated javascript file")
-
+ val bootstrapSnippet = settingKey[String]("piece of javascript to make things happen")
implicit val system = ActorSystem(
"SystemLol",
config = ConfigFactory.load(ActorSystem.getClass.getClassLoader),
@@ -46,7 +47,7 @@ package object workbench extends sbt.Plugin {
localUrl := ("localhost", 12345),
fileName := "workbench.js",
server := {
- implicit val server = system.actorOf(Props(new SocketServer))
+ implicit val server = system.actorOf(Props(new SocketServer(bootstrapSnippet.value)))
val host = localUrl.value
io.IO(Sockets) ! Http.Bind(server, host._1, host._2)
server
@@ -57,8 +58,8 @@ package object workbench extends sbt.Plugin {
new Logger {
def log(level: Level.Value, message: => String): Unit =
if(level >= Level.Info) server.value.send(Json.arr("print", level.toString(), message))
- def success(message: => String): Unit = server.value.send(Json.arr("print", message))
- def trace(t: => Throwable): Unit = server.value.send(Json.arr("print", t.toString))
+ def success(message: => String): Unit = server.value.send(Json.arr("print", "info", message))
+ def trace(t: => Throwable): Unit = server.value.send(Json.arr("print", "error", t.toString))
}
}
val currentFunction = extraLoggers.value
@@ -68,16 +69,29 @@ package object workbench extends sbt.Plugin {
streams.value.log("Reloading Pages...")
server.value.send(Json.arr("reload"))
},
+ updateBrowsers := {
+ println("partialReload")
+ server.value send Json.arr("clear")
+ ((crossTarget in Compile).value * "*.js").get.map{ (x: File) =>
+ println("Checking " + x.getName)
+ FileFunction.cached(streams.value.cacheDirectory / x.getName, FilesInfo.lastModified, FilesInfo.lastModified){ (f: Set[File]) =>
+ println("Refreshing " + x.getName)
+ server.value send Json.arr("run", f.head.getAbsolutePath, bootstrapSnippet.value)
+ f
+ }(Set(x))
+ }
+ },
generateClient := {
- val clientTemplate = IO.readStream(getClass.getClassLoader.getResourceAsStream("workbench_template.ts"))
- val transformed = clientTemplate.replace("<host>", localUrl.value._1).replace("<port>", localUrl.value._2.toString)
- val outputFile = (crossTarget in Compile).value / fileName.value
- IO.write(outputFile, transformed)
- outputFile
+ FileFunction.cached(streams.value.cacheDirectory / "workbench"/ "workbench.js", FilesInfo.full, FilesInfo.full){ (f: Set[File]) =>
+ val transformed = IO.read(f.head).replace("<host>", localUrl.value._1).replace("<port>", localUrl.value._2.toString)
+ val outputFile = (crossTarget in Compile).value / fileName.value
+ IO.write(outputFile, transformed)
+ Set(outputFile)
+ }(Set(new File(getClass.getClassLoader.getResource("workbench_template.ts").getPath))).head
}
)
- class SocketServer extends Actor{
+ class SocketServer(bootstrapSnippet: String) extends Actor{
val sockets: mutable.Set[ActorRef] = mutable.Set.empty
def receive = {
case x: Tcp.Connected => sender ! Tcp.Register(self) // normal Http server init
@@ -97,6 +111,7 @@ package object workbench extends sbt.Plugin {
case Sockets.Upgraded =>
sockets.add(sender)
println("Browser Open n=" + sockets.size)
+ self send Json.arr("eval", bootstrapSnippet)
case f @ Frame(fin, rsv, Text, maskingKey, data) =>
sockets.foreach(_ ! f.copy(maskingKey=None))
@@ -105,7 +120,6 @@ package object workbench extends sbt.Plugin {
if (sockets.contains(sender)) println("Browser Closed n=" + sockets.size )
sockets.remove(sender)
-
case x =>
}
}
diff --git a/readme.md b/readme.md
index d1675b1..762073c 100644
--- a/readme.md
+++ b/readme.md
@@ -16,7 +16,6 @@ To Use
- Clone this from Github into a local directory
- Add a dependency onto the scala-js-workbench project, e.g. in `project/project/Build.sbt`
- Add `scala.js.workbench.buildSettingsX` to your project settings in `project/Build.sbt`
-- Add `refreshBrowsers <<= refreshBrowsers.triggeredBy(packageJS in Compile)` to your project settings, to make it refresh every time `packageJS` completes.
- Modify the `packageJS` task with the following setting, to make it generate the snippet of `workbench.js` file needed to communicate with SBT:
```scala
@@ -25,9 +24,27 @@ packageJS in Compile := {
}
```
-With that done, when you open a HTML page containing `workbench.js`, if you have sbt running and scala-js-workbench enabled, it should connect over websockets and start forwarding our SBT log to the browser javascript console. You can run the `refreshBrowsers` command to tell it to refresh itself, and if you set up the `triggeredBy` rule as shown above, it should refresh itself automatically at the end of every `packageJS` cycle.
+- Define your `bootstrapSnippet`, which is a piece of javascript to be run to start your application, e.g. `bootstrapSnippet := "ScalaJS.modules.example_ScalaJSExample().main();"`. scala-js-workbench requires this so it can use it to re-start your application later on its own. You do not also need to include this on the page itself, as scala-js-workbench will execute this snippet when the browser first connects.
-Current still sort of flaky; in particular, it does not behave properly across `reload`s in SBT, so if the refreshes stop working you may need to `exit` and restart SBT.
+Now you have a choice of what you want to do when the code compiles:
+
+refreshBrowsers
+===============
+`refreshBrowsers <<= refreshBrowsers.triggeredBy(packageJS in Compile)`
+
+This will to make any client browsers refresh every time `packageJS` completes.
+
+updateBrowsers
+==============
+`updateBrowsers <<= updateBrowsers.triggeredBy(packageJS in Compile)`
+
+This will attempt to perform an update without refreshing the page every time `packageJS` completes. This involves returning the state of the `body` to the initial state before any javascript was run, stripping all event listeners and clearing all repeated timeouts and intervals. This is a best-effort cleanup, and do things like clear up websocket connections or undo modifications done to `window` or `document`, but should suffice for most applications.
+
+-------
+
+With that done, when you open a HTML page containing `workbench.js`, if you have sbt running and scala-js-workbench enabled, it should connect over websockets and start forwarding our SBT log to the browser javascript console. You can run the `refreshBrowsers` command to tell it to refresh itself, and if you set up the `triggeredBy` rule as shown above, it should refresh/update itself automatically at the end of every `packageJS` cycle.
+
+Current still sort of flaky; in particular, it does not behave properly across `reload`s in SBT, so if the refreshes stop working you may need to `exit` and restart SBT. Also, the initial page-load/refresh while the caches are first being set up may cause things to misbehave, but refreshing the page manually once should be enough for it to stabilize.
Depends on [SprayWebSockets](https://github.com/lihaoyi/SprayWebSockets) for its websocket server; this will need to be checked out into a local directory WebSockets next to your SBT project folder. See this repo (https://github.com/lihaoyi/scala-js-game-2) for a usage example.
diff --git a/workbench_template.ts b/workbench_template.ts
index 9577787..35376d1 100644
--- a/workbench_template.ts
+++ b/workbench_template.ts
@@ -1,5 +1,12 @@
var socket = (function(){
var open = false
+ var shadowBody = null
+ window.onload = function(){
+ console.log("Loaded!")
+ shadowBody = document.body.cloneNode()
+ console.log("Loaded!X")
+ }
+
var start = function(){
socket = new WebSocket("ws://<host>:<port>/")
socket.onopen = function(event){
@@ -8,10 +15,33 @@ var socket = (function(){
}
socket.onmessage = function(event){
var data = JSON.parse(event.data)
+
if (data[0] == "reload") {
console.log("Reloading page...")
location.reload()
}
+ if (data[0] == "clear"){
+ document.body = shadowBody.cloneNode()
+ for(var i = 0; i < 99999; i++){
+ clearTimeout(i)
+ clearInterval(i)
+ }
+ }
+ if (data[0] == "run"){
+ var tag = document.createElement("script")
+ var loaded = false
+ tag.setAttribute("src", data[1])
+ if (data[2]){
+ tag.onreadystatechange = tag.onload = function() {
+ if (!loaded) eval(data[2]);
+ loaded = true;
+ };
+ }
+ document.head.appendChild(tag)
+ }
+ if (data[0] == "eval"){
+ eval(data[1])
+ }
if (data[0] == "print") console[data[1]](data[2])
}
socket.onclose = function(event){