In this post, we’ll discuss the advantages of using ClojureScript and re-frame to build a robust, maintainable single-page application.
We’ll see why functional reactive programming has become a best practice for frontend development, and why Clojure’s stability, immutable data structures and functional paradigms provide a pleasant and safe development experience.
Why choose ClojureScript over JavaScript?
If you’ve been following the saga of front-end development, you’ve probably spent considerable time trying to make sense of the myriad frameworks, build tools, and language developments.
You’ve probably even heard the term JavaScript framework fatigue used to describe the difficulty in following the pace that frameworks emerge, only to be replaced a few months later by something shinier.
You’re probably asking yourself, is there a way to make use of the best tools for user interfaces and program architecture that doesn’t involve changing frameworks every couple of months?
And, if you’re not a big fan of JavaScript as a language, you’re probably wondering whether you could switch to a solid, stable language such as ClojureScript whose features have changed very little since its inception?
ClojureScript, a functional lisp targeting JavaScript
ClojureScript is a lisp dialect that compiles to JavaScript for use in a browser and other environments where JavaScript runs, such as nodejs on the server.
The language is about seven years old, has remained remarkably stable during that time, has been deployed in production environments since the very beginning, and is currently in use by industry-leading companies such as CircleCI and Exoscale.
It features a simple syntax, immutable data structures, functional paradigms, and a powerful standard library.
It’s compiled using Google’s Closure Compiler, allowing for dead code elimination and other performance-enhancing tricks that make having a compile-to-JavaScript language feasible in settings where performance matters.
To give you a taste of the language, here’s the infamous “left-pad” function in JavaScript, along with its more elegant, functional counterpart in ClojureScript:
// JavaScript
function leftpad (str, len, ch) {
str = String(str);
var i = -1;
if (!ch && ch !== 0) ch = ' ';
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
; ClojureScript
(defn left-pad
([s len]
(left-pad s len " "))
([s len ch]
(let [width (count s)
pad-width (- len width)]
(cond->> s
(pos? pad-width)
(str (reduce str "" (repeat pad-width ch)))))))
Not only can we avoid the confusing variable assignments (setting i to -1, for example) that can result in “off-by-one” errors, but we can define functions that take multiple arities, allowing for better code reuse and separation of concerns.
Finally, we also avoid modifying the string in place, leaving us with clean, readable code that produces predictable outputs given the same inputs.
Other reasons to consider ClojureScript over JavaScript include its handling of concurrent programming without callbacks using a CSP-inspired core.async library, its spec library for specifying and validating data and programs, and other useful libraries.
It’s also worth mentioning that ClojureScript tends to be fairly concise, saving the programmer a lot of typing and making the codebase easier to maintain, debug and refactor.
ClojureScript’s advantages over JavaScript for frontend development
What about programming for the browser, though, where we’ll mostly be dealing with user interfaces and generating views of data?
ClojureScript provides at least two distinct advantages over JavaScript in this regard.
First, its functional nature aligns well with React, which has rapidly become the leading front-end library, not only in terms of number of downloads and deployments but also in user satisfaction.
Second, ClojureScript’s use of s-expressions as its syntactic unit of measurement makes expressing React components even more natural than using React’s build-in rendering templates.
How to use React in ClojureScript
To work with React in ClojureScript, we can import the library directly, or use one of several libraries that provide helpful features such as a component rendering syntax and an approach to dealing with events.
We’ll have a look at re-frame, whose component rendering syntax is very elegant (it uses ClojureScript functions to define a component, and simple vectors to define HTML elements that comprise the component), and which supports a clean flux-style approach to handling events and interactions using immutable views of data.
How to build a single-page application in re-frame: a toy SPA
Let’s take a look at building single-page application that takes adavantage of the flux architecture and functional reactive programming.
We’ll employ ClojureScript and re-frame in this task, walking you through the major moving parts and highlighting a few advantages of using this approach.
Our final result will be a toy application that calculates ping-times to Exoscale’s object storage endpoints from your location.
The source code repository is freely available if you would like to try running on your own machine.
Installing ClojureScript
Installing ClojureScript only takes a couple of minutes following the quick start guide.
Once installed, you’ll be ready to start creating your own applications.
To follow along with our application, you’ll also need to install Leiningen, one of Clojure’s build programs that helps with managing depedencies and orchestrating build steps.
Once Leiningen is installed you should be able to cd
to the root project directory and type:
lein figwheel dev
After a few seconds (the first time you run will require downloading the project dependencies), you’ll be able to open your browser to http://localhost:3449 and see the application running.
Thanks to figwheel, a live code reloading library, as you edit the program, changes will be reflected automatically in the browser without having to reload the page.
A quick start on Reagent and re-frame
Reagent is a ClojureScript library that allows access to the JavaScript React using nice ClojureScript functions and also provides an HTML templating language comprised of native Clojure data structures, vectors and maps.
It lets you create an HTML element as follows: [:div#main {:style {:width 200}} "hello"]
. That creates a div with id main and a width attribute of 200 pixels.
You’ll notice the conciseness and also the fact you don’t have to remember to close the div
element at the end.
Re-frame is a library for reagent that provides a helpful pattern for doing functional reactive programming, making use of a event dispatch and handling routines and the ability to have elements in the browser respond and update according to those events.
It provides a repeatable set of abstractions for handling browser events and user interactions, and makes organizing the associated side effects clean and efficient.
Architectural overview of a reagent / re-frame application
Our application consists of:
- an in-browser database
- event registrations
- subscriptions to those events
- views
- a core namespace that ties these together and initializes our app with an initial database value and view
All re-frame apps begin by mounting the root React component on the DOM somewhere using reagent, a library with which the re-frame library is tightly integrated:
(reagent/render [views/main-panel]
(.getElementById js/document "app"))
Here, reagent places our main view main-panel
inside the div with id app
in the browser’s DOM.
We also define an asynchronous loop that sends ping requests every two seconds to each of the endpoints:
(async/go-loop []
(let [zones @(re-frame/subscribe [::subs/zones])]
(doseq [zone zones]
(re-frame/dispatch [::events/ping zone]))
(async/<! (async/timeout 2000))
(recur)))
At each iteration, this loop schedules ping events to each zone in our database, and then blocks for two seconds.
re-frame’s reactive, unidirectional data flow
Now for the first clever feature of re-frame: we can dispatch
events in response to user actions (or any other exogenous event such as timer clicks in the snippet above) that can be followed by subscribers and force re-renders of the browser content.
We’ll explain those subscribers in a second, hang on.
Next, in our views namespace, we define the various components such as the main panel, the zone latency boxes, and the zone details widgets that are shown when the user clicks on an individual datacenter.
When we say these views are functional, it means that we define a function such as f(data) = view that takes data as an input and then produces a view that the DOM (handled nicely by React in this case) can display.
Now for the second brilliant design feature of re-frame: views can subscribe to those events that are of interest to them, and have their content re-rendered in the browser when those inputs change.
Therefore, in the events and subs namespaces, we define events that can take place and the subscribers to those events, respectively.
An example event is the ::good-http-result
that is trigged on a successful response from the SOS server:
(re-frame/reg-event-db
::good-http-result
(fn-traced [db [_ zone]]
(let [latency (- (.getTime (js/Date.)) (get-in db [:ping zone :sent-at]))]
(assoc-in db [:ping zone :latency] latency))))
Once the latency is calculated, it’s inserted into the in-memory “database” using Clojure’s assoc-in
to place the value at the specified location, and a subscriber in the zone-latency
view picks up on the changed value and updates it on the map in the browser.
State management in re-frame using a functional programming approach
Re-frame makes use of an in-memory database to store application data.
It’s represented as a map
in Clojure which has handy methods for getting, setting, and filtering the database.
In the db namespace in our app, we define a default-db
that provides initial values for our application whose values will change over time based on the subscriptions we define above.
It’s a nice way of keeping all one’s “state” in a safe place, and a safe way of atomically getting/setting values by different parts of the application.
It also forces discipline on programmers to keep state changes well accounted for, and makes debugging an application much easier.
Ultimately, this state management approach produces safer code with fewer bugs.
Summary: ClojureScript offers you a better way to build singe-page apps with re-frame and reagent
This separation of concerns makes it easy to follow the directional flow in our app: a user triggers an event (such as a click on a datacenter location on the map) which calls the ::show-details
event, whose subscriber is waiting in the views namespace to display details to the user.
All of this has been accomplished without relying on a tangle of callbacks, without promises or other bespoke asynchronous mechanisms, and without polluting the views code with conditional behaviours and indirection.
In addition, extending our application to handle more user interactions or additional sources of data is straightforward: we add events and subscriptions and modify our views accordingly knowing that downstream consumers of our actions have well-defined contracts as to how they’ll use our data.
How to deploy your application?
There are many options to deploy your application. But we think one of the most convenient options is to deploy and host your single-page application on Object Storage! This makes for a simple, cheap and reliable alternative to host self-contained stateless applications.