Clojurdle: Wordle Written in ClojureScript

February 20, 2022 | 10 min. read


TL,DR: What it says on the box, play it here !

Inspiration

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.

ClojureScript, however, is something that I haven't dabbled with before this project. As a web developer, I've always had an ambivalent relationship with JavaScript. It's certainly not the most rock-solid Or ecosystem, for that matter. As thousands have said before, I think if JS had a little less churn in its community and a more robust standard library, it would do wonders for the language. but it's probably the best we've got when it comes to the browser. WebAssembly has been "almost here" for the past decade, and every framework-specific solution like Hotwire/ Livewire/ LiveView seems experimental at best (not to mention they use JS deep down anyway, with some extra framework lock-in to boot). As such, I was sceptical of ClojureScript.

Since ClojureScript is (for most intents and purposes) just Clojure compiled to JavaScript rather than JVM bytecode with some added bells and whistles for the DOM, there's a lot I like about it. The good ol' parentheses, Clojure data structures, and higher-order functions like map, filter, 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 defonce and atom, you can redefine functions during development without having to do full page reloads or throw away state during testing. Very cool!

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 massive example"-style documentation and 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 example using -M be more appropriate? but both are solid and there's a good consensus within the community on what should be used.

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 shadow-cljs because:

  1. It seemed the most similar to Leiningen, with a single shadow-cljs.edn file controlling all dependencies and configuration,
  2. It manages both NPM dependencies and CLJS jars,
  3. 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 I didn't get around to creating the "Share your score" feature, or tracking score distributions across days. I figure it'd just be myself and close friends/family playing, so there was no need to implement the social features.

You might also notice the :keyword.with.class.soup everywhere, 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 here ) is really cool. The closest thing I can think of in mainstream JS land is Vue's :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 def'd it in main.wordlist to prevent cluttering up main.core. I also decided to import my only other dependency, cljs-time, since doing date math with JavaScript's builtin Date class seems like not a good time. The resulting one-liner 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!