Ambly Require Reload
Ambly remote compilation explained.
How do edits you've made to ClojureScript source make it to your iOS device when using Ambly? What really happens behind the scenes?
It turns out that the answer is—intentionally—not too different from how existing ClojureScript REPLs, like the shipping Node and browser REPLs, work. The approach was glossed over in this post, but here I'd like to pop the hood and go into a little more detail.
Let's say, in
src/foo/core.cljs, you add a definition for a new function,
(defn square [x] (* x x))
and then in the Ambly REPL, issue
(require 'foo.core :reload)
followed by evaluating
9. How does this work?
require is a ClojureScript REPL special. It is actually implemented in the base
cljs.repl code—nothing special is done by Ambly. Its execution is triggered by simply detecting that a list form has been entered with the symbol
'require as its first element.
require is accomplished by first converting it to an
ns form with a
:require spec and then evaluating it. For our example, this would look like
(ns cljs.user (:require foo.core))
cljs.repl sees that it is evaluating an
ns form, it loads any dependent namespaces, calling
load-dependencies, which calls
cljs.compiler namespace. (You’ll see all of this in the resulting call stack if you happen to have an error in your source.)
Now normally, the compiler output would be written in
out (or to a place configured via
:output-dir.) But when an Ambly REPL session is established, it provides an override for the
:output-dir setting and “redirects” the ClojureScript compiler to write to a different directory, say
During app launch, the Objective-C side of Ambly starts up a WebDAV server within the iOS app (which is running on your device or in the simulator) and, when connecting, the Clojure-side causes the operating system to mount the network file system exposed by that WebDAV server (on
/Volumes/Ambly-A8EC7B89 for our example).
Effectively, the end result of all of this is that the ClojureScript compiler's output is no longer written to a conventional directory on your computer's disk, but instead to a directory in your iOS app's sandbox, indirectly via the WebDAV mount point.
Getting back to
foo/core.cljs, along with any other compiler output (such as compiler analysis metadata cache and source mapping files) to be written to your app's sandbox.
But, this alone is not enough for
In this case,
cljs.repl delegates back to Ambly, by calling
-load, and Ambly satisfies this by sending a
The ClojureScript compiler makes use of the Google Closure dependency management system. Even for a REPL, Closure is involved, even though no minifying optimization are being done, with compilation being done in
A lot of steps, but: No magic!
The subsequent form
(foo.core/square 3) is handled by the Clojure-side of Ambly by regular means: The base
If you look at the way Ambly works given the description above, you can see that a lot of the description has more to do with
David Nolen came up with this beautifully simple design well before Ambly was fleshed out. I like how it leverages existing tooling and support to achieve something quite magical without involving any actual magic.