A new world: writing CSS in ClojureScript and life after Sass

We hit a milestone recently — we totally replaced Sass (and Ruby) in our project with Garden, a Clojure based alternative. We’re super excited about this because it changes the game for us in multiple ways.

When we decided to build a SaaS product, we spent a lot of time investigating what other tools people hired to do the job of estimating work.

Unsurprisingly, spreadsheets are a huge deal in our area. There are lots of great reasons why (speed, simplicity and reliability), and plenty of pain points to improve too.

In this context we also spent some serious time researching tech stacks too. Ultimately we landed on Clojure(Script) as the language and Hoplon as a way to handle HTML.

We think Hoplon is cool because it brings a lot of the simplicity and reliability of spreadsheet programming to the web browser but it doesn’t tackle CSS generation beyond what is achievable within the browser.

Garden vs. Sass

Sass is one of the original CSS preprocessors, and is still probably the most popular preprocessor around. At its heart, it is a powerful templating system for CSS that, if you squint, is almost a “real programming language” (hey, it’s Turing complete!).

We’re not aware of anyone seriously using Sass for anything other than generating CSS, but within that domain Sass is definitely king.

Garden, on the other hand, is nothing but a simple bridge between Clojure’s most basic data structures (vectors and hash maps) and rendered CSS. There are some helper functions/macros that make building those data structures a little easier, but ultimately Garden is just a 1:1 map from Clojure data to CSS data.

Want an example?

; Clojure data (Garden compatible)

[:div {:color :red}] ; CSS data

div { color: red; }

This Clojure data is in a format called EDN. Roughly, EDN is to Clojure what JSON is to JavaScript.

That’s right, we’re generating CSS from something like JSON.

It’s really much better than you think.

For a long time we ignored Garden for a few reasons:

We already know Sass — learning Garden would be a real investment

Sass is super popular — we perceived a lot of value in the ecosystem

Sass is familiar — we stopped noticing the apple stickers long ago

Our codebase was tiny — scaling pains hadn’t really started.

However, as we grew the codebase (it’s still pretty small) and started getting more serious about what we were allowing ourselves to spend time on, we were finding ourselves battling Sass more and more.

So, here’s a high level list of the best things we really like about our transition to Garden. They fall into two camps, things that most people could benefit from and Sass not playing nice with other components of our stack.

No more merge conflicts

One problem we faced with Sass is that the ecosystem is dominantly Ruby based. Yes, LibSass exists and the Java implementation has Clojure wrappers, but the obvious integrations with Boot (a Clojure build tool) didn’t cover basic features like globbing.

Ultimately, this meant that we ran Sass as a standalone process to build our CSS files that we then committed directly to the git repository for deployment. Our experience with other Sass projects in various tech stacks is that this configuration is not ideal but not entirely uncommon either.

For a while, whenever we were reviewing our work kanban-style we noticed a consistently large number of tickets in the final QA step before deployment. If you’re familiar with constraint theory, you’ll know that a buildup like this is a big red flag — something is bottlenecking production at the point where work is piling up.

Tickets getting through, but only just.

The thing is, this final QA step is largely automated and should never have a lower throughput than any of the prior steps that all require human intervention. For us, inventory was building up at the automated testing stage for one really annoying reason: every CSS ticket we delivered, in Sass, was generating a minified CSS file, and so every single ticket merged to master caused a merge conflict in every other branch up for merging.

It is still early days for us and we have no fixed visual identity or confirmed style guide. The CSS is pretty rough and subject to a lot of change so we need a workflow to support that.

We had a few options:

Figure out how to get Boot to compile Sass in the way we wanted

Try an alternative to Sass that works more naturally with Boot

Automate the process of resolving merge conflicts in CSS with a cron job

Limit CSS changes to one or two in QA at any time.

As Garden is just a Clojure library, it worked perfectly with Boot. In fact, the community provided integration totals about 50 lines of code.

We even implemented an alternative to globbing in just 20 lines of code — taking inspiration from the Clojure testing frameworks — that allows us to simply tag anything we want to have appear in our CSS from literally any part of our Clojure codebase.

; I can put this *anywhere* and it will be compiled into screen.css (def ^:screen foo

[:div {:color :red}])

The win here is twofold, not only can we write and construct CSS inline with all our other code but we have a lot more flexibility in how we can structure our projects. We can keep doing things more or less the way that we always have with Sass, but there are new options available to us now as well.

Data structures and abstractions

Sass files in the wild are can be relatively flat with hundreds of small chunks of code or big, deeply nested and complex structures. Mixins and other constructs that inject chunks into other chunks to make bigger chunks are ubiquitous and useful to help us spot patterns and keep the ever mounting complexity somewhat maintainable.

In many ways this is just the nature of data. CSS, HTML, XML, JSON, YAML, etc… it’s all the same in the end — all the little details and complexities in the raw data quickly mount and we go looking for tools and abstractions to reign it in.

Clojure is designed to build these abstractions and Rich Hickey (creator of Clojure) embraces data structures as a basis for programming in general. Seriously, watch a few of his talks and you’ll get a feel for how much thought has been put into this.

There are many programming languages where even the basic premise of something like Garden would be horribly awkward and misguided. Really, the only reason Garden works at all is because the native data structures and functionality of Clojure happen to fit CSS preprocessing perfectly.

Perfect.

When we started porting the bits we liked from the Sass libraries were using, we expected it to be a huge task. We discovered that a lot of the code in these libraries was either implementing things already in Clojure — contains? , even? , first , last , string.replace , reverse , etc. — or could be simplified with the tools available in a more general programming environment.

When we really thought about what we were using Bourbon and Neat for, we realised that most CSS libraries exist for two reasons: to provide browser compatibility shortcuts, and to provide useful functions that most people don’t want to write themselves.

We don’t have plans to offer a lot of legacy browser support — and if we did PostCSS is a very appealing option. Even without PostCSS, the Garden compiler offers a simple, configurable way to automatically vendor prefix properties at the compiler level, without the need for additional library code.

The “useful functions” provided by Sass libraries can’t compare to what can be done with direct access to all of Clojure. Although obviously if you’re comfortable writing your own ruby gems, that’s another story.

It really hasn’t been a huge, or even particularly difficult task to move what we needed from Sass to Garden at all. As an example, here is our complete port of Neat’s span columns (simplified to block display elements only).

(def ms (->> mesh.typography/scales

:golden

(mesh.typography/modular-scale-fn 1))) (def column (ms 3))

(def gutter (ms 1)) (def grid-columns 12) (defn percentage

[n]

(-> n double (* 100) (str "%"))) (defn width

"Width calculation pulled from _private.scss in neat."

[columns]

(+ (* columns column)

(* (dec columns) gutter))) (defn flex-gutter

[container-columns]

{:pre [(< 0 container-columns)]}

(->> container-columns

width

(/ gutter)

percentage)) (defn flex-grid

[columns container-columns]

{:pre [(<= 1 columns container-columns)]}

(->> [columns container-columns]

(map width)

(reduce /)

percentage)) (defn span-columns

"Adapted from span-columns in neat. Uses the default block display."

[& {:keys [columns container-columns]

:or {container-columns grid-columns}}]

[ :& {:float :left

:display :block

:margin-right (flex-gutter container-columns)

:width (flex-grid columns container-columns)}

[:&:last-child {:margin-right 0}]])

To compare with Sass, the original spans these 3 files.

Clojure data and abstractions can go places that Sass can’t (although, again, if you regularly write your own gems this might seem less impressive). Want an infinite repeating list of colours that you can dip into at any time?

(cycle ["#fff" "#ccc" "#000"])

Want a curried version of your layout function with the first argument locked to 100px?

(def narrow-layout (partial default-layout "100px"))

Want to incorporate values stored in a database of user preferences? Easy.Want advanced colour spaces and palettes? No problem. Breakpoint and media query management? 3 lines of code. Unit tests for layout logic? Why not?

We also enjoyed working with Garden and icon fonts, passing through variable names and writing functions that spit out :before elements with unicode content strings. Our best attempt in Sass was still pretty naive and verbose. The Clojure version just felt more natural to us.

This code takes a configuration hash map we can maintain in the form {:icon-name :e123} and provides a simple function that we can call like (iconmaker :google :small) to handle everything.

(def codes

{:google :ea8a

:slack :e93a

...

:file-excel :e939}) (defn code->icon-str

[code]

{:pre [(keyword? code)]}

(str “\”\\” (name code) “\””)) (def icons

(->> codes

(map (fn [[k v]] [k (code->icon-str v)]))

flatten

(apply hash-map))) (def icon

{:font-family "icomoon"

:speak "none"

:font-style "normal"

:font-weight "normal"

:font-variant "normal"

:text-transform "none"

:line-height 1

:padding-right (u/px 10)

:-webkit-font-smoothing "antialiased"

:-moz-osx-font-smoothing "grayscale"}) (defn iconmaker

[glyph size]

{:pre [(keyword? glyph) (keyword? size)]}

[(s/& (s/before))

(merge

icon

{:content (glyph styles.icons/icons)}

(case size

:small

{:font-size (styles.typography/ms 1)

:position "relative"} :big

{:font-size (styles.typography/ms 2)

:top (u/px 28)

:left (u/px 32)

:position "absolute"}))])

One language stack

Deleting Ruby from our project means a whole lot of good stuff. Firstly — we can avoid using multiple dependency management systems. It was with great joy that we deleted the Gemfile and Bundler cruft added to the project just for dependency management around generating CSS. It was also exciting to delete the .scssc files SASS uses as an internal cache but cruftily leaves on the file system.

Secondly, the whole setup, deployment and maintenance of the project is simplified. Sass, being a Ruby gem, requires the right version Ruby configured and installed. That means that not only our local environments have to run the right version of Ruby (not being ruby experts, we’ve had some serious headaches with previous Mac OS versions and RVM fails) but if we want our server to compile Sass as part of deployment, we need it to have Ruby installed too. It’s just one more devops overhead we didn’t need.

Goodbye Ruby. You will not be missed.

Cross-compiling

Much more exciting than removing Ruby is the prospect of cross-compilation of CSS. Our backend is based on Clojure (Java) while our front end is ClojureScript (JavaScript). Garden cross-compiles, which means that unlike Sass, values used for CSS can be passed between the front and back-end easily. If you’ve used the Meteor JavaScript framework, it’s kind of like that.

One easy way to understand how not having this can make life hard is to think about colour management in Sass projects. Usually the app’s branding palette is set as Sass variables, which means that it’s also stuck in Sass/CSS land forever. It’s not such an unusual situation that branding information needs to be available to more code than just CSS. For example, consider the need to pass a colour palette to a charting library.

In Garden, however, variables for CSS are variables that can be used in the rest of your project. That also means classes, IDs, data attributes, and any other property that can be used in CSS is also available to the same language that generates markup and interacts with the database. The possibilities are huge.

We wrote a three line integration between Garden and Hoplon that lets us dynamically compile into the DOM, at runtime, any Garden data that was available at build time on the deployment server.

For a lot of front-end devs, Webpack is currently the best way to get close to this kind of solution, and there are ideas for how you can at least pass variables into Sass from your app (but not back out). A native cross-compilation solution is much better as it is completely transparent and the support is as good as the language itself.

A logical step towards a pattern library approach

Nested patterns. :3

We had tried our hardest to split our code in a way that was both component-centric and reusable if needed, but what had transpired with Sass was kind of neither.

With the cross-compiling as mentioned above, the project now is set up wonderfully for a pattern library approach, where we can group our Garden files and markup together easily in folders in our project structure. Sass doesn’t naturally lend itself to this kind of inline organisation — unless we do want to use something like Webpack. And even in that case, from what I understand of Webpack, it compiles the Sass to CSS when it includes it in the module so you can’t really “reach in” to the Sass and play with the variables or classnames or whatever anyway.

Styled Components kind of gets closer to what we’re doing but the mangled classnames and flat CSS is a big tradeoff that we can avoid through cross compilation. If we do want to take the Styled Components approach, it is always just one macro away.

Namespaces and scoping

The job of being an engineer is really just about being organised. There have been many attempts to make Sass and CSS work in a structured way, but in our experience all of them require tons of discipline to maintain and struggle in the face of real world “edge cases”.

We think we’ve tried almost every way of making Sass file and code structures work at scale. Using Garden we have a couple of extra tools available from Clojure that really change the game. Clojure’s scoping rules and namespaces in particular are quite cool. The CSS itself obviously still spits out as CSS and still is a cascading waterfall of specificity, but in terms of our code organisation, we now have the ability to create reusable patterns that we can repeat inside each component. Of course, you can do this in every language to some extent but Clojure beats Sass here hands down.

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton

Naming things is hard and we didn’t want to make it harder. If we find a set of names that works and is reusable, we wish to reuse it. We’re stoked that we can.

Dependency management

Sass-globbing was a really great improvement on the Sass workflow in general. Previously, we had a massive file of import statements that we had to update with every new file we added, and in the correct order. Sass-globbing made it possible to just add new files and have them imported automagically, which was a really big deal.

The only problem was that it forced us to create a whole bunch of “shared by everything” files that we had to import before we did more component-based code. This is pretty normal and a pretty standard way of getting around the whole C part of CSS.

The dependency management side of Garden is pretty cool. It does at least as much as was available in Sass, with maybe some added extras. One thing that is cool about Garden (and Clojure(Script) in general) is that I can import one function from a file and not the entire file. So even if I have a file for all my layout patterns, I don’t actually have to import the entire file every time I want to use it — I can just import the function I need to use.

In Sass, we relied very heavily on the partials underscore file format to make sure Sass didn’t compile our shared styles over and over again. We also aggressively split out mixins from what we called “mappings” so we could have our re-usable styles stored in one place and apply those re-usable styles to selectors in a different place to preserve separation of content and presentation.

All of that was essentially a replacement for what Garden provides by default. What we have with Garden is just a lot simpler.

In conclusion

If you’re working on a Clojure project that’s still using Sass, check out Garden. CSS preprocessors are a good idea and Sass has reigned supreme for a while now, but there are new approaches and libraries emerging that lend themselves to a simple pattern library approach.

In terms of startup tech stacks, Clojure + Clojurescript + Hoplon + Garden is serving us really well, and we would recommend to anyone looking to build a scalable, solid front-end stack without the overheads of including Ruby (and endless compiled CSS merge conflicts) in your project.

If you are doing something similar, or have any questions, reach out in the comments! And as always — if you’re interested in seeing what we actually made, head to estimate-work.com.