Modular frontend applications in ClojureScript.
The most fundamental problem in computer science is problem decomposition: how to take a complex problem and divide it up into pieces that can be solved independently.
― John Ousterhout, A Philosophy of Software Design
As frontend applications become larger and more complex, or when multiple teams need to work on different features simultaneously, it becomes necessary to structure and organize the code in a way that allows for independent development, maintenance, and deployment of distinct units. Moira is a library designed to accomplish just that based on three core principles.
- Modular: Manage complexity and limit cognitive load by separating concerns into encapsulated units with well-defined boundaries and explicit contracts
- Event-driven: Reduce coupling between parts of the system through dependency injection and a central event log
- Adaptable: Optimize for flexibility by embracing the dynamic nature of ClojureScript and the browser environment
Unlike Reagent or UIx, Moira is not yet another ClojureScript React React wrapper. It does not directly interact with the render cycle, and there is no preference on which UI library to use for component building and instrumentation.
Moira is not intended to be a complete framework for building web-based applications like re-frame or Fulcro. Moira's event system and application state complement UI events and UI state management, rather than replacing them.
That being said, the all-state-in-one-place philosophy of re-frame's global application state does not play well with Moira's main objective of creating self-sufficient components for managing encapsulated module state independently.
Add Moira to your project dependencies:
{:deps
{com.pitch/moira {:mvn/version "v0.8.0-alpha"}}}
Configure and start a system
for bootstrapping your application:
;; configure modules and dependencies
(def system {:router {:start #'router/start}
:session {:export #'session-manager/export
:start #'session-manager/start}
:ui {:deps #{:session}
:resume #'ui/hot-reload
:start #'ui/start
:state {:el (.getElementById js/document "app")}}})
;; create application instance
(defonce app (application/create system))
;; bootstrap application
(defn ^:export init []
(application/start! app))
;; instrument hot reload
(defn ^:dev/before-load stop []
(application/pause! app))
(defn ^:dev/after-load start []
(application/resume! app))
To set up the project (e.g., install npm dependencies), execute the
init
task once:
bb init
Starting an environment for interactive development, you can serve an
in-browser test runner at http://localhost:8031
and have everything
recompiled automatically on any change with:
bb test:browser
You will then be able to connect to the app via nrepl
on the port returned
by:
cat .shadow-cljs/nrepl.port
Learn about all available tasks with:
bb tasks
For a single execution of the test suite in headless mode, invoke:
bb test:once
It is also possible to automatically recompile and run the headless test suite on changes:
bb test:watch
Update CHANGELOG and README. Commit changes.
git commit -a -m "Prepare release."
Tag commit with the new version and description. Push tagged commit.
git tag -a v0.1.0 -m "First release."
git push --follow-tags
Check version and publish release on Clojars.
bb release:version
CLOJARS_USERNAME=username CLOJARS_PASSWORD=clojars-token bb release:publish
Published under the MIT License.
Copyright © 2021-2023 Pitch Software GmbH