You’re probably thinking two things as you read this:
- What is ClojureScript?
- 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:
| |
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”:
| |
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:
| |
…while in Clojure, you’d simply have:
| |
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:
[]denotes a vector, resembling anArrayList.()indicates a sequence, much like aLinkedList.
Here’s how you create a list in Clojure[Script]:
| |
Sequences are defined slightly differently:
| |
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:
| |
This behavior allows you to define a function without immediately executing it! Only a ‘bare’ sequence is evaluated during program execution.
| |
We’ll see why this is relevant later on!
Functions can be defined in several ways:
| |
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:”
| |
“Into strings of HTML like this:”
| |
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:
| |
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?
| |
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:
| |
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:
| |
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:
| |
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:
| |
- Wrapping components:
| |
- Dynamic attribute injection:
| |
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:
| |
| |
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:
| |
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 tobuild.gradleorpackage.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!