ClojureScript/Clojure event-driven application framework. Successor to Carry.
-
Carry framework is very strict about separating signals (side effects) and actions (model changing functions). In a long run it turned out to produce a lot of boilerplate (e.g. many signal handlers are anemic and only dispatch an action). Such separation was mainly needed for implementing Redux-ish time traveling debugger, but in the end I don’t find such tool too useful, it’s pretty easy to debug the app using traditional approaches.
-
By default Carry recommends using keywords for events and actions. It makes it cumbersome to maintain the codebase, because IDEs don’t understand this abstraction.
re-frame has an implicit global state and hard dependency on Reagent.
Also it’s based on patterns which are too heavyweight:
-
Subscriptions (vs., for instance, Reagent reactions or Rum derived atoms).
-
Effects & co-effects (vs. usual function and/or method calls and using mocks in tests).
-
Interceptors (vs. middleware pattern).
-
Async event handling queue.
-
Events can be defined as vars (so that IDE can jump to definition and compiler can detect undefined events).
-
Event definitions look similarly to
defn
(so that IDE can highlight arguments, etc.). -
Events can be async.
-
Events can be serialized/deserialized. E.g. it’s possible to record and replay user’s actions.
-
No enforced separation between "side-effectful" and "pure" events.
-
Testable. It’s possible to test event handlers similarly to usual functions and also mock the events.
-
Supports injecting additional functionality via third-party packages (such as logging, routing, etc.). See https://github.com/metametadata/aide/tree/master/contrib.
-
Proposes a pattern for SPAs (single app atom, view/view-model separation).
-
Framework core is agnostic to UI and model layers.
-
Proposes app lifecycle pattern (via standard start/stop events). This is needed for "stateful" third-party packages.
-
(TODO) Time traveling debugger.
Define an app which has some model:
(ns app.core
(:require [aide.core :as aide]))
(let [*model (atom {:val 0})
app (aide/object {:*model *model})]
,,,)
Define an event which modifies the app model:
(aide/defevent on-increment
[app _data]
(swap! (:*model app) update :val inc))
Define an event which emits another event asynchronously:
(aide/defevent on-delayed-increment
[app delay-ms]
(.setTimeout js/window #(aide/emit app on-increment) delay-ms))
Emit events into the app:
(aide/emit app aide-lifecycle/on-start)
(aide/emit app on-increment)
(aide/emit app on-delayed-increment 1000)
(aide/emit app aide-lifecycle/on-stop)
Middleware example:
(defn add-logging
"Will log all events."
[object]
(update app :aide.core/emit
(fn wrap-emit
[original-emit]
(fn emit
[object event data]
(println (str event) (pr-str data))
(original-emit object event data)))))
(def app-with-logging (-> app
add-logging))