Clojurdle: Wordle Written in ClojureScript
February 20, 2022 | 10 min. read
TL,DR: What it says on the box, play it here !
Like most people caught up in its viral success, my girfriend and I have been playing Wordle daily for the past few weeks. The core puzzle design of the game is great, but the social aspects are just as much fun; I doubt there's a greater joy in life than solving the day's word in two guesses, and being able to show all your friends (that are willing to listen, anyway).
As such, when it was revealed that the game had been sold to the New York Times for a price in "the low seven figures", we were a little disappointed and predicted the imminent monetization of something we'd been enjoying for free. So, I embarked on a journey to steal fire from the gods and write my own version - in ClojureScript!
ClojureScript Per Se
I've been getting more into Clojure within the past year or so, and I've really enjoyed the experience. I've always been a big fan of functional programming, but Clojure is in a league of its own when it comes to both language design and developer experience. It supports the usual paradigm of designing your program as a series of transformations on data (rather than a set of imperative instructions), but the pervasive use of fundamental data structures rather than classes and interfaces keeps the language small and simple. The REPL has also ruined me as a programmer - I don't think I could go back to a language where I can't see the evaluated results of my code within my editor as I'm writing it. Other people have shilled Clojure far better than I ever could, so I'll just leave it at that.
Since ClojureScript is (for most intents and purposes) just Clojure compiled
for the DOM, there's a lot I like about it. The good ol' parentheses,
Clojure data structures, and higher-order functions like
reduce, etc. are all there. The REPL is
there too, and one of the more useful features is the ability to call
js/ namespace functions within the REPL and see them run in the
browser. For example, evaluating
(js/alert "hi there!") within
your REPL-connected editor will cause the alert window to fire within your
browser. Pairing this with some clever use of
atom, you can redefine functions during development without
having to do full page reloads or throw away state during testing. Very
Welcome To The Jungle
The Clojure ecosystem has been slowly moving towards deps.edn and tools.build, but Leiningen is still a fair majority's build
tool of choice and has been the de-facto standard for the better part of a
decade. I'm not in love with Leiningen's "here's a
deps.edn is similarly
For example, if you run
clj --help, half the options just say
something like "Use concatened aliases to modify classpath...", which
doesn't quite describe what the command does. Similarly, the guide has
an example for "Using a main" that uses
-X. Wouldn't an
-M be more appropriate?
both are solid and there's a good consensus within the community on what should
ClojureScript is not so unified, however. There seems to be several dominant
project managers/build tools out there at the time of writing - should I
depend on NPM packages, or CLJSJS jars? Should I use figwheel, or shadow-cljs? The Quick Start has an
example just using a
deps.edn-like command, do I even need a
third party tool? All this choice leads to decision paralysis, or simply
picking something and cargo-culting because it's far too complex a decision
to evaluate the entire ecosystem at once. As for me, I just chose
It seemed the most similar to Leiningen, with a single
shadow-cljs.ednfile controlling all dependencies and configuration,
- It manages both NPM dependencies and CLJS jars,
- It had the best documentation by far.
There is one library that's supremely popular and practically ubiquitous in ClojureScript, however, and that's Reagent. Reagent is a CLJS wrapper around ReactJS that honestly feels more-React-than-React. There's been a big push in the React world towards functional programming; your components and what they render should just be a pure function of their state and props. ClojureScript, being a functional language, fits this paradigm like a glove. Combined with Hiccup for homoiconic HTML, writing frontend user interfaces has never been easier.
Where There's a Word, There's a Way
Here is the bulk of the source for Clojurdle. As you can see, it's quite dense - I was able to get most features implemented in about ~130
You might also notice the
and that's because I also decided to use TailwindCSS for styling. I chose
this because a) I suck at doing any kind of styling work without it, and b)
this was more of a project to learn CLJS rather than CSS. The class soup is
still pretty offensive, and I would not be surprised if the
:keyword.dot.class syntax does not play nice with certain
Tailwind classes. It's definitely a tradeoff, though. Since the Hiccup
template that the component is returning is just code, being able to
reactively call CLJS to return different classes and their styling (as seen
) is really cool. The closest thing I can think of in mainstream JS land is
:class bindings, but (as with all things Vue) it's very hard
to tell when something is a string or when it's code. With CLJS the
distinction is irrelevant, since code is data and data is code.
The "word of the day" feature was also surprisingly easy to implement. I
found a word list online of common five-letter words and
main.wordlist to prevent cluttering up
main.core. I also decided to import my only other dependency,
class seems like not a good time. The
to index into the
word list based on the current day is pretty nice. I still feel a little
guilty about pulling in an extra dependency just for this one line, though -
if there's a nice way to do it in plain CLJS, please let me know!
That's a Wrap
I think that's all I can really say about the project. It was in total perhaps a weekend's worth of work spread over a few weeks, so a short and fun project. I couldn't call it an enormous learning experience since (as stated) CLJS is designed to be as close to Clojure as possible. I also can't say if I'll be universally reaching for CLJS for frontend projects from now on, but it'd be a frontrunner the next time I'm doing a Clojure web project.
Correction, Feb. 21 2022: Just as I was making this post public, I found that someone else in the Clojure community made a Wordle clone and posted it in the Clojurians Slack just a few days before this post. Here's the GitHub repo, and here's a pretty cool walkthrough of the project. That guy's version is also written in plain ClojureScript (other than shadow-cljs, no Reagent or anything), so I'd definitely check it out!