Discovering ClojureScript for Front-end Development

You’re probably thinking two things as you read this:

  1. What is ClojureScript?
  2. This doesn’t apply to me.

Don’t jump to conclusions! I’m here to change your mind. Give me 10 minutes, and I’ll show you how ClojureScript makes building front-end, React-like apps enjoyable, efficient, and above all, functional.

Getting Started with ClojureScript

  • Don’t worry about knowing Lisp. I’ll break down any code examples in this post!
  • If you’re keen on some pre-reading, I highly recommend https://www.braveclojure.com/, your go-to resource for starting with Clojure (and by extension, ClojureScript).
  • Clojure and ClojureScript share the same language foundation—I’ll often use Clojure[Script] to refer to both.
  • I’m assuming you have some familiarity with React and front-end development in general.

ClojureScript in a Nutshell

Short on time but curious about ClojureScript? Let’s start with the basics.

The ClojureScript website puts it succinctly: ClojureScript is a compiler for Clojure that targets JavaScript. It emits JavaScript code which is compatible with the advanced compilation mode of the Google Closure optimizing compiler.

Here’s what makes ClojureScript stand out:

  • Multiparadigm Flexibility: It embraces multiple programming paradigms, leaning towards the elegance of functional programming, known for enhancing code readability and helping you do more with less code.
  • Immutable by Design: Immutability is built-in, eliminating a whole class of runtime headaches!
  • Data-Driven Architecture: In ClojureScript, code is data. Most applications boil down to functions operating on data structures, making debugging a breeze and code exceptionally clear.
  • Simplicity at its Core: ClojureScript is easy to pick up—no cryptic keywords or hidden magic.
  • A Standard Library That Has It All: You name it, it’s probably there.

Let’s dive into an example:

1
2
3
4
(defn component
  []
  [:div
    "Hello, world!"])

A note for those unfamiliar with Lisp or ClojureScript: Focus on :div, [], and (). :div represents the <div> element. [] signifies a vector (like an ArrayList in Java), and () represents a sequence (similar to a LinkedList). We’ll explore these in more detail later!

This is the most basic React component in ClojureScript—a keyword, a string, and a few lists.

That’s it? you might say. It doesn’t look that different from a JSX or TSX “hello world”:

1
2
3
4
5
6
7
function component() {
  return (
    <div>
      "Hello, world!"
    </div>
  );
}

But don’t be fooled! Even in this simple example, there are significant differences:

  • No Embedded Languages: ClojureScript uses strings, keywords, and lists, maintaining a unified syntax.
  • Concise and Elegant: Lists provide all the expressiveness we need without the verbosity of closing HTML tags.

These seemingly small distinctions have a big impact, not just on how you code, but also on how you express yourself through your code!

Curious to see how? Let’s dig deeper into what makes ClojureScript special…

ClojureScript Fundamentals

In this tutorial, I’ll try not to get too deep into the technical intricacies of Clojure[Script] (although there’s plenty to explore!). However, understanding a few key concepts will help you grasp the possibilities.

If you’re already familiar with Clojure and Lisp, feel free to skip to the next section!

Let’s cover three essential concepts:

Keywords

Clojure[Script] introduces the concept of Keywords. Think of them as a hybrid between constant strings (like in Java) and keys. They act as symbolic identifiers that evaluate to themselves.

For instance, the keyword :cat will always represent :cat and nothing else. In Java, you might write:

1
2
3
4
5
6
7
8
9
private static const String MY_KEY = "my_key";

// ...

myMap.put(MY_KEY, thing);

// ...

myMap.get(MY_KEY);

…while in Clojure, you’d simply have:

1
2
(assoc my-map :my-key thing)
(my-map :my-key) ; equivalent to (:my-key my-map) ...nice and flexible!

Note: In Clojure, a map serves as both a collection (key-value pairs, similar to a Java HashMap) and a function to access its contents. How cool is that?

Lists

As a Lisp dialect, Clojure[Script] leans heavily on lists. Remember the two key distinctions:

  1. [] denotes a vector, resembling an ArrayList.
  2. () indicates a sequence, much like a LinkedList.

Here’s how you create a list in Clojure[Script]:

1
2
3
4
[1 2 3 4]
["hello" "world"]
["my" "list" "contains" 10 "things"] ; you can mix and match types 
                                     ; in Clojure lists!

Sequences are defined slightly differently:

1
2
'(1 2 3 4)
'("hello" "world")

We’ll explain the leading ' in the next section.

Functions

Finally, let’s talk about functions. In Clojure[Script], a function is a sequence written without the ' at the beginning. The first element of the list is the function name, followed by its arguments. Here’s an illustration:

1
2
3
4
5
(+ 1 2 3 4)               ; -> 10
(str "hello" " " "world") ; -> "hello world"

(println "hi!")           ; prints "hi!" to the console
(run-my-function)         ; runs the function named `run-my-function`

This behavior allows you to define a function without immediately executing it! Only a ‘bare’ sequence is evaluated during program execution.

1
2
(+ 1 1) ; -> 2
'(+ 1 1); -> a list of a function and two numbers

We’ll see why this is relevant later on!

Functions can be defined in several ways:

1
2
3
4
5
6
7
8
9
; A normal function definition, assigning the function
; to the symbol `my-function`
(defn my-function
  [arg1 arg2]
  (+ arg1 arg2))
; An anonymous function that does the same thing as the above
(fn [arg1 arg2] (+ arg1 arg2))
; Another, more concise variation of the above
#(+ %1 %2)

A Deeper Dive

With the basics covered, let’s examine how things work behind the scenes.

React integration in ClojureScript commonly relies on a library called Reagent. Reagent leverages Hiccup and its syntax for representing HTML. Here’s a snippet from the Hiccup repo’s wiki:

“Hiccup transforms Clojure data structures like this:”

1
[:a {:href "http://github.com"} "GitHub"]

“Into strings of HTML like this:”

1
<a href="http://github.com">GitHub</a>

In essence, the first element of the list becomes the HTML element type, and the remaining elements form its content. You can optionally provide a map of attributes to be attached to the element.

Nesting elements is as simple as nesting lists within their parent element’s list. Let’s illustrate with an example:

1
2
3
4
[:div
  [:h1 "This is a header"]
  [:p "And in the next element we have 1 + 1"]
  [:p (+ 1 1)]]

Observe how we can seamlessly incorporate any Clojure function or syntax within our structure without explicit embedding mechanisms. It’s just a list!

What happens when this code is evaluated at runtime?

1
2
3
4
[:div
  [:h1 "This is a header"]
  [:p "And in the next element we have 1 + 1"]
  [:p 2]]

We get a list of keywords and content! No complex types, no hidden magic—just a straightforward list. You have complete freedom to manipulate and work with this list directly—what you see is what you get.

With Hiccup handling the layout and Reagent managing logic and events, we have a fully functional React environment.

Building Complexity

Let’s bring it all together by creating some components. One of React’s (and Reagent’s) strengths is the ability to modularize view and logic into reusable components.

Consider a simple component displaying a button with some basic logic:

1
2
3
4
5
6
7
8
9
; widget.cljs

(defn component
  [polite?]
  [:div
   [:p (str "Do not press the button" (when polite? ", please."))]
   [:input {:type "button"
            :value "PUSH ME"
            :on-click #(js/alert "What did I tell you?")}]])

A note on naming: Modules in Clojure are typically namespaced. For instance, widget.cljs might be imported under the namespace widget, making the component function accessible as widget/component. I prefer a single top-level component per module, but you’re free to use names like polite-component or widget-component.

This component presents us with a potentially polite widget. The expression (when polite? ", please.") evaluates to ", please." when polite? == true and to nil otherwise.

Let’s embed this widget into our app.cljs:

1
2
3
4
5
(defn app
  []
  [:div
   [:h1 "Welcome to my app"]
   [widget/component true]])

We embed the widget by calling it as the first item in a list, just like HTML keywords! Additional children or parameters can be passed as subsequent elements within the same list. Here, we pass true, so polite? is true within our widget, resulting in the polite version.

Evaluating our app function at this stage would yield:

1
2
3
4
5
[:div
   [:h1 "Welcome to my app"]
   [widget/component true]] ; <- widget/component would look more like a 
                            ;    function reference, but I have kept it 
                            ;    clean for legibility.

Notice that widget/component remains unevaluated! (Refer back to the Functions section if needed.)

Components in the DOM tree are only evaluated (transformed into actual React objects) when updated, ensuring smooth performance and reducing complexity.

For those seeking a deeper understanding, further details can be found in the Reagent docs.

Lists, Lists Everywhere

It’s crucial to note that the DOM is represented as a nested list structure, and components are simply functions returning these nested lists. Why is this significant?

Because any operation applicable to functions or lists can be applied to components.

This is where using a Lisp dialect like ClojureScript pays off—components and HTML elements become first-class citizens, manipulated like any other data! Let me reiterate that:

Components and HTML elements are treated as first-class objects within the Clojure language!

You read that right. Lisps are practically designed for list processing (which is no coincidence!).

This opens up possibilities like:

  • Mapping over numbered lists:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(def words ["green" "eggs" "and" "ham"])

(defn li-shout
  [x]
  [:li (string/uppercase x))

(concat [:ol] (map li-shout words)

; becomes

[:ol
 [:li "GREEN"]
 [:li "EGGS"]
 [:li "AND"]
 [:li "HAM"]]
  • Wrapping components:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
; in widget.cljs

(defn greeting-component
  [name]
  [:div
   [:p (str "Hiya " name "!")]])

; ...

(def shouty-greeting-component
  #(widget/greeting-component (string/uppercase %)))

(defn app
  []
  [:div
   [:h1 "My App"]
   [shouty-greeting-component "Luke"]]) ; <- will show Hiya LUKE!
  • Dynamic attribute injection:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(def default-btn-attrs
  {:type "button"
   :value "I am a button"
   :class "my-button-class"})

(defn two-button-component
  []
  [:div
   [:input (assoc default-btn-attrs
                  :on-click #(println "I do one thing"))]
   [:input (assoc default-btn-attrs
                  :on-click #(println "I do a different thing"))]])

Working with fundamental data types like lists and maps simplifies development compared to classes, proving far more powerful in the long run!

The ClojureScript Way

Let’s summarize what we’ve learned.

  • Simplicity is Key: Elements are lists, and components are functions returning elements—everything boils down to fundamental building blocks.
  • Power of First-Class Objects: Treating components and elements as first-class objects allows us to write more concise and expressive code.

These points embody the essence of Clojure and functional programming—code is data to be manipulated, and complexity arises from connecting simpler parts. We represent our program (in this case, a webpage) as data (lists, functions, maps), maintaining this representation until Reagent transforms it into React code. This approach fosters code reusability and ensures readability with minimal magic.

Styling Your App

Now that we know how to build basic functionality, let’s add some visual flair. The simplest approach is using stylesheets and referencing classes within components:

1
2
3
.my-class {
  color: red;
}
1
2
[:div {:class "my-class"}
  "Hello, world!"]

This produces the expected result—a stylish, red “Hello, world!” text.

However, why separate styling into a stylesheet when we’ve gone through the effort of co-locating view and logic within components? It introduces the overhead of managing two languages and two separate files.

Why not write CSS as code within our components (notice a pattern here?)? This offers several advantages:

  • Centralized Component Definition: Everything related to a component resides in one place.
  • Guaranteed Unique Class Names: Clever generation techniques ensure class name uniqueness.
  • Dynamic Styling: CSS can respond to data changes, creating a more interactive experience.

My preferred method for CSS-in-code is using Clojure Style Sheets (cljss). Embedded CSS would look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
;; -- STYLES ------------------------------------------------------------

(defstyles component-style []
  {:color "red"
   :width "100%"})

;; -- VIEW --------------------------------------------------------------

(defn component
  []
  [:div {:class (component-style)}
   "Hello, world!"])

The defstyles function generates a unique class name, which is particularly beneficial when importing components.

Cljss offers numerous other features, including style composition, animations, and element overrides, which I won’t delve into here. I encourage you to explore its capabilities further!

Putting It All Together

Finally, let’s discuss the glue that holds everything together. Fortunately, apart from a project file and an index.html, boilerplate is minimal.

You’ll need:

  • Project Definition File (project.clj): A staple in Clojure projects, this file defines dependencies—even directly from GitHub—and build configurations, akin to build.gradle or package.json.
  • Entry Point (index.html): Acts as the anchor for your Reagent application.
  • Setup and Initialization: Some minimal code to set up your development environment and launch the Reagent application.

You can find the complete code example for this ClojureScript tutorial available on GitHub.

That’s it for now! I hope this has sparked your interest, whether it’s exploring Lisp dialects (Clojure[Script] or others) or trying your hand at building a Reagent application. I promise you won’t regret it.

Stay tuned for a follow-up article, “Getting Into a State,” where I’ll discuss state management using re-frame—think Redux, but in the ClojureScript world!

Licensed under CC BY-NC-SA 4.0