Scalable Styles in Production JS

Matt Krick
Frontend Weekly
Published in
7 min readOct 25, 2016

Can you feel it? The dust is finally settling for the JavaScript community. I won’t call the past few years “fatigue” because hey, who gets angry when they ask for a new toolbox, get 3 for free, and have the luxury of picking their favorite? Sounds like the best problem ever. Admittedly, it was a challenge to keep up with the latest developments in the JavaScript world. New module loaders, transpilers, front-ends, back-ends, data transports, state managers, client caches, test runners… heck, even the language is new! And like some meritocratic utopia, the good ones found their way into production applications and a community emerges to maintain and improve them. Now, we’ve gotta tackle styles.

After vjeux’s talk in 2014, a plethora of new ways to make css suck less started popping up. For the sake of brevity, they can be broken down into 3 camps: css files, inline styles, css in js.

Write a CSS File

This includes everything from glorified namespacing (BEM, OOCSS, SMACSS) to preprocessors like SASS, LESS, and stylus, to css-modules + postcss.

My original Meatier.io framework used this approach, specifically css-modules. Unfortunately, it still suffered from modularity problems. Imagine a production SPA that has a landing page, admin page, and user portal. It’s your job to use webpack to code split the 3. Webpack will still only generate a single css file. This is because if it generated 3 separate css files, it couldn’t guarantee that the css would arrive & load before the JS does, which would cause a flash of unstyled content (FOUC), as shown below:

To mitigate this, you could write your css files and use webpack to turn them into style tags. Ignoring the extra cost to process css files in your webpack build, you’re still constrained by css (or icss) as a language. It is a crummy language. The reason why React got so popular is because it gave you the full power of JavaScript in your view layer, so why settle for less when it comes to styles? Furthermore, since it’s a different language, you can’t use the same theme file for css and JS. As your app grows, you will need to use JS inline styles for an icon, 3rd party component, email template rendered with Oy, etc. So if you want to be DRY, shrink your payload, minimize build time, and avoid the chain of pre/post processing, writing a css file is out.

Write inline styles

These are methods that attach styles to individual DOM elements, the two most popular being React itself and Radium. React offers lightweight tooling by providing a style attribute, but this doesn’t work for state-based props like :hover or pseudos like :after. You’d have to use React’s onMouseEnter event instead. This is exactly what Radium does for you under the hood, which is just brilliant. Unfortunately, this doesn’t work great for things like media queries or native styles like :visited. If you have a large, responsive (mobile + desktop) app, those media queries will load late causing a desktop to see the mobile view for a split second (assuming mobile-first architecture). That’s game over. Again, in a demo app, you’ll never see the difference. In a production app, by the time you notice, it’ll be too late.

Write CSS in JS

These are styles written in JS that are converted to css at runtime & injected into a style tag. In other words, it’s real css. Sounds like I’m splitting hairs, but the difference is very important when it comes to performance. Examples include fela, JSS, and Aphrodite.

When we first wrote Action, we used react-look, the now-deprecated predecessor to fela. It had a React Native version with an identical API, support for global styles (think fonts & 3rd party css overrides), SSR support, and the inline-styles-prefixer that Aphrodite uses was written by the author. All great things! Unfortunately, to achieve all of this, it used a higher-order component (HOC) that traversed all the child components. These traversals got so expensive that when webpack loaded a new code chunk, the UI would noticeably lock up, as the CPU profiling confirms:

That 3.9 seconds includes other stuff, too. But spoiler alert — the same action now takes < 300ms.

The only thing we could do was tear it out of all 200+ components & start fresh. When we did that, I opted for the package with the smallest amount of code so I could hack on it if needed. That turned out to be Aphrodite.

Aphrodite is by no means perfect. It lets you write globals next to your modular code, which can get smelly. It defaults to appending !important to each style it injects. Styles injections are async, meaning if you work directly on DOM nodes, you’l need to re-render on the next tick. Injections are also an O(n) operation, meaning the larger the app, the slower it goes. It doesn’t natively support runtime-defined styles (themes). That said, these shortcomings are all manageable.

How to power up Aphrodite

Global Styles

The first step is injecting global styles. The new, official recommendation is to write an extension, create a singleton, and be careful. In my experience, that’s a recipe for spaghetti. I enjoy software that is opinionated and makes it hard to write bad code (React, for example). So, I prefer to add globals by using Aphrodite’s internal function:

import {injectStyleOnce} from 'aphrodite/lib/inject';
export default function injectGlobals(globalStyles) {
Object.keys(globalStyles).forEach(name => {
injectStyleOnce(name, name, [globalStyles[name]], false, true);
});
}

I can call this injectGlobals from my root component once and be done with it. In 6 lines! If a team member wants to add a global style, they just add it to the globalStyles object. Having one home for global styles is headache insurance. It means I don’t have to concern myself with tricky singletons, extensions, global collisions, or load order.

No-important

Second, Aphrodite defaults to appending !important to each style. They do this for legacy reasons and because Aphrodite expects you to have other styling tools in your arsenal. If it’s your only one (and it should be, since you now know how to use it with globals) then you can use their no-important version. Do one thing, do it well.

DOM Injections

Aphrodite currently appends new rules to the innerHTML of their style tag. When innerHTML gets modified, the browser has to reparse & re-evaluate the entire contents, an O(n) function. Think of it like a string in JavaScript: if I write foo = “style” + “s” it doesn’t really append “s” to “style”, it creates a whole new string 1 char at a time. That means all the styles get reloaded & if you’re unlucky enough to load custom fonts, then your fonts will flicker. If you have a lot of styles, it could even cost you an animation frame, so there goes your 60fps webapp. My solution was to turn the injection into an O(1) operation by inserting rules instead of modifying innerHTML. Doing that obviates the need for the prefixAll dependency on the client and since it’s so cheap, you don’t have to inject styles asynchronously anymore. So in the future, we could have snappier styles and a smaller client payload. Note: these changes come from my personal fork and may never get merged into the Aphrodite source code.

Themes

Finally, let’s say you want to allow your users to pick out a nice theme. In other words, your styles are a function of state. So, I decided to build a withStyles HOC. The idea isn’t new, react-with-styles already exists, but I wanted one that didn’t heavily rely on singletons or require destructuring all my css calls, and offered the ability to build styles based on props. For example, say you have a component that can vary widely in style:

<Button size="big" palette=“warm” fill="outline" text="Pay Me!" />
<Button size="small" palette=“cool” fill="solid" text="Get paid" />

Let’s say that each attribute like size has 5 options. That requires anywhere from 15 to 5³ classes that we’d have to create! Now let’s say the warm palette is user-defined. The upper bound just turned into infinity. Sure, you could use React’s inline style attribute to compute this as a one-off case, and hope your theme designs are simple, but what if the designers want something crazy, like :visited to be the color of the user’s theme? That’s where it becomes critical to generate styles based on context.

To the future

I hope this gives you a better understanding of the current state of styles. It’s still too early to declare Aphrodite the winner, since there are still some glaring issues with it. However, CSS in JS in most definitely the path forward. To date, about 88% of the JS community has never even heard of Aphrodite, which tells me that styles are such a hard problem, most folks just say screw it and stick to what they know. For us at Parabol, we’re so happy with Aphrodite (well, our fork of it) that we’re building our entire component library using Aphrodite + Redux. By using Redux, our components don’t need all the hooks for callbacks in the API because the state is public. By using Aphrodite, we escape the huge performance burden of inline styles that plague some libraries (like material-ui) and we don’t require folks to use our flavor of preprocessor just to tweak a style. Day by day, web dev keeps getting more fun.

--

--