Skip to content

Latest commit

 

History

History
130 lines (85 loc) · 5.29 KB

clojurescript.md

File metadata and controls

130 lines (85 loc) · 5.29 KB

Managing state in ClojureScript

In case you need to manage state in ClojureScript using mount, all the mount Clojure features are supported in ClojureScript. Which means all the mount Clojure documentation is the mount ClojureScript documentation.

With a slight change in mode ( no change in mood though, just the mode :)).

The "Why"

Since reader conditionals were added in Clojure 1.7, it became a lot easier to target both platforms with lots of code reuse. You might have noticed that most of mount code lives in .cljc files.

The way mount is designed it "mounts" itself to a solid Clojure namespace API, and while .cljc helps a lot with targeting Clojure and ClojureScript, JavaScript VM is vastly different from JVM. Since JavaScript mostly tagrets browsers, mobile devices and IoT, it is quite important to compress the final result.

Which means that Clojure namespaces API are not that well supported in ClojureScript, since they get renamed and optimized during compilation + of course no native namespace support on the JavaScript side (but that is somewhat solved with Google Closure).

But. When developing an application in Clojure and ClojureScript, it would only make sense if the API for any library would be identical for both platforms. It should be transparent for developers whether they use a library in Clojure or ClojureScript. It is not possible for all libraries (i.e. concurrency, reified Vars, etc.), but we should try to make it possible for most.

Mount Modes

Mount has two modes clj and cljc.

Just Clojure Mode

clj mode is default, and all the APIs are exactly the same as they are in the mount Clojure documentation.

Clojure and ClojureScript Mode

cljc is not a default mode, but it is easy to switch to:

To switch Mount into this mode do:

(mount/in-cljc-mode)

anywhere before a call to (mount/start), usually at the entry point of an app: in the -main, web handler, etc.

This sets mount into the cljc mode. In this mode mount supports both: Clojure and ClojureScript with one difference from the default clj mode:

all states are "derefable"

which means in order to use them, you'd need to @ it. That's where the difference between the two modes ends.

Again, cljc mode API is consistent across both Clojure and ClojureScript.

While initially it may sound strange, this approach has very nice properties:

  • Mentally something that you deref (@) is associated with a state behind it
  • The whole system may start lazily without an explicit call (mount/start)
  • States may have watchers which is just an idea at this point, but it could be quite useful

No need to call (mount/in-cljc-mode) on ClojureScript side, it is only called once on the server (Clojure) side.

note: (mount/in-cljc-mode) does not require the code to be .cljc, just a geeky name to convey the support for both modes: Clojure and ClojureScript

Now that the theory is laid out...

Mounting that ClojureScript

Let's look at the example ClojureScript app that uses mount to manage several states:

  • Datascript Database
  • Websocket Connection
  • Configuration

In order to run it, just compile cljs (in :advanced mode, because why not? :)) with:

$ boot cljs-example
Started Jetty on http://localhost:3000
nREPL server started on port 64412 on host 127.0.0.1 - nrepl://127.0.0.1:64412
Adding :require adzerk.boot-cljs-repl to mount.cljs.edn...
Compiling ClojureScript...
• mount.js

And just open a browser at http://localhost:3000:

The flow behind the app is quite simple:

  • load config
  • open a WebSocket connection
  • keep an audit log in Datascript
  • call (mount/stop) to disconnect

Using States

A good example of derefing state is here in websockets.cljs:

(ns app.websockets
  (:require [app.conf :refer [config]]
            [app.audit-log :refer [audit log]])
  (:require-macros [mount.core :refer [defstate]]))

;; ...

(defstate system-a :start (connect (get-in @config [:system-a :uri]))
                   :stop (disconnect system-a))

notice how config is deferef'ed @config in order to use its state. It of course does not have to be deref'ed here, and can be just passed along to the connect function to be @ed there instead.

Thanks

I'd like to thank these good people for brainstorming and supporting the idea of Mount in ClojureScript universe:

@DomKM, @yogthos and @edvorg