Complexities of using CSS in modern web apps and how ClojureScript can leverage Garden to generate style sheets at runtime
We attempt replacing all style management magic provided by webpack with about 60 lines of ClojureScript and, arguably, achieve astounding success.
Some people think that CSS is about building cascades of styles. It is not, fortunately. Sadly, newcomers to web development tend to confuse cascading style inheritance, which is a good thing, with a necessity to build said cascades themselves using oh so helpful tools such as LESS or SASS. This leads to a unmaintainable mess of code which you’ve probably encountered multiple times in different projects.
Well, you get a general idea. Somehow people are trying to:
a) manually do work that a browser is supposed to do, having a CSS engine and all that;
b) delay having to think about architecture by using complex macro-like magic.
Confronted with such level of complexity, the best course of action is usually
to throw it all away, stop and
rethink your life priorities let computer do
as much of your job as possible. And this is exactly what libraries like
css-loader4 do currently. You just require your CSS
files as you would your regular
*.js files and receive a map containing all
classes from imported style sheet.
button.css file might look like snippet below. Note, that because
css-loader prefixes all classes, there is a need to denote cases where
prefixing should be omitted. For this, following syntax is used:
Everything is magically included, CSS is parsed, all classes camel cased for
your convenience and, more importantly, automagically prefixed to include
file’s name in class names (i.e.
.button gets turned into
.fancy-button__button based on name of file wherein CSS file was
included, or whatever prefixing settings you specify). This ensures class
names won’t collide and you had to do pleasantly little to enjoy it.
But this creates another problem: of managing large number of CSS files. While before we had everything taken care of by diligent SASS compiler, now we are forced to rely on a plugin inside webpack. And while access to CSS classes in JS is a great improvement, we still can’t manipulate our styles through code in a consistent way. Things like CSS variables5 promise us some flexibility, but given the time it takes to implement such controversial features in all major browsers, we shouldn’t rely on them in foreseeable future. CSS and JS still live in completely different and largely isolated worlds – without DOM acting as a glue there is no easy way of accessing styles that are to be applied.
In a project I’m currently working on, we have a rather complicated way of building CSS bundle. It involves separately compiling SASS files, then running through webpack dependency graph identifying and transforming required CSS files, running autoprefixer an all those files and finally concatenating everything and minifying result.
While this is obviously capable of producing production-ready results, I don’t
think one would call it straightforward. Too much magic is happening inside of
webpack, too many moving parts and all sorts of configuration is needed to make
this work in a desirable way. Moreover, there is regrettably no control over, or
indication of, order parts of CSS are concatenated in. More than once this forced
me to resort to
!important in unobvious places.
The running theme in Clojure world is to simplify everything until things are so simple it would be very difficult to simplify them any further.
When you look at React wrappers, for instance, you’ll notice that, with a notable exception of OM6, they mostly focus on using pure functions for rendering, simple atoms for state and some form of array-based HTML description language7.
Continuing this tradition, we’ll use Garden8 to manage our stylesheets. In order to achieve feature parity with our previous setup we need to implement following:
- Generated stylesheets with prefixed classes;
- Inclusion of appropriate styles;
- A convenient way to generate prefixed classnames.
To have something to start from, lets take a simple
Icon component and use it as a baseline.
Here we define some simple styles, create a RUM component, then include styles, and use prefixed class names in component’s template.
Now all we need is to define a minimal implementation of our style sheet management thing so that our component will at least render.
Garden can do many things, including, but not limited to, unit calculations, color manipulation and, obviously, it can generate CSS.
While I’m not entirely sure using hiccup-like syntax for CSS is a good thing, we’ll leave this discussion for later time. For now, let’s agree this is a manageable way to represent complex AST of a modern CSS preprocessor and roll with it.
After some testing, it appears that all modern browsers are fast enough at concatenating strings. Except Firefox. This little bastard managed to add almost 100ms every time styles were rendered. Thus, because memory is cheap and I sure didn’t want to spend time investigating why Firefox is the only one behaving this way, memoizing9 style generation function was chosen as a passable remedy for those annoying delays.
Now that we have a string with CSS, we need a way to load it into a browser. Also, we need a way to rename class names in such a way, that would ensure we bind nicely to CSS classes we generated previously.
Here, everything is pretty straightforward. Styles get inserted in a style with a specific id, or, if such id already exists, styles get updated with new ones.
A RUM mixin tries to update styles when component tries to mount itself.
Obvious added benefit of this approach is naturally hierarchical order of
<style/> elements in
It is reasonable to assume that using
(ns-name *ns*) as a fallback prefix
value would simplify default use case, but I haven’t tried it yet.
Bundling CSS inside JS payload is nothing new12
and is obviously not of everyone. Benefit of reducing the number of moving
parts, reducing number of cached/requested files and radically simplifying
manifest.json my be desirable for B2B apps, electron13
wrappers, webapps14 and generally less
initial-load-time-sensitive apps, is rather an impediment if you develop highly
latency-optimized or content-heavy sites. But then you probably wouldn’t be
using Clojurescript in the first place, I think.
While being able to manipulate units15 and macro-like media-queries and keyframes16 in Garden are a nice thing to have, in my experience have a somewhat limited use.
What I found a lot more powerful is ability to seamlessly integrate Clojurescript with stylesheets. Use functions to generate code, access DOM and/or JS APIs whenever needed, generate CSS without having to put up with whatever syntax SASS decided was OK to have for it’s mixins.
As you’ve probably guessed by now, one can easily compose styles by requiring needed pieces and adding them as a node whenever needed. Keeping libraries of often-used styles will enable not only easy reuse, but efficient tree-shaking of unused branches, provided for free by Clojurescirpt compiler.
It is pretty obvious that approach described above isn’t even remotely ideal. It’s not intended to be. The idea was to reduce perceived complexity and make underlying style manipulations predictable and manageable. Optimizing for a larger set of constraints is left as an exercise for the reader.
https://facebook.github.io/react-native/docs/style.html “Styles in React Native” ↩
https://css-tricks.com/the-debate-around-do-we-even-need-css-anymore/ “Do We Even Need CSS Anymore?” ↩
https://developer.mozilla.org/en-US/docs/Web/Manifest “Web app manifest” ↩
https://github.com/noprompt/garden/wiki/Units-&-Arithmetic “garden units” ↩
https://github.com/noprompt/garden/wiki/Compiler#vendors “garden keyframes” ↩