4.9 KiB
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 importand 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 mode is is not default, but it is easy to switch to it:
(mount/in-cljc-mode)
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 two modes end.
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 defer (
@) 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
Now as 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:
$ lein do clean, cljsbuild once prod
Compiling ClojureScript...
Compiling "dev/resources/public/js/compiled/mount.js" from ["src" "dev/cljs"]...
Successfully compiled "dev/resources/public/js/compiled/mount.js" in 23.966 seconds.
And just open a browser at file:///[path-to-mount]/mount/dev/resources/public/index.html:
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: