CSS in JS in CSS

Once upon a time, HTML was the only language on the web. Styling was done with tables and color tags. Then CSS came along, and changed how we though about design. CSS selectors grew to let you target almost any element on your page in many many ways. Inline styles were shunned, to improve maintainability, and keeping you HTML free from junk.

A little down the road, we are told not to use the power of CSS selectors. We are now told to only use classes, and nothing else. And we’re now left to painfully go through our markup and styles and create a set of classnames that are unique but general.

At the same time CSS is still missing many features. In 2015, we can’t rely on CSS to have variables. Vjeux from the React time detailed these problems a few months ago in this talk.

Vjeux concludes that we should write our CSS in javascript for all the benefits that come from using a real programming language. He then suggests that we use inline styles as class names are now meaningless. He drives home that because we no longer have HTML (its all javascript with React), we no longer have any of the downsides of using inline styles.

We are going from this:

<div className="my-heading">

to this:

<div style=styles.myHeading>

A string is replaced with a variable and we get rid of CSS completely. As a side benefit, using only inline styles removes stylesheet parsing selector matching, and actually improves performance.

This approach is quite great, especially on the upcoming React-Native. However, on the web CSS is still useful for certain things that are unnecessarily complicated in javascript, like :hover, :active etc. Sadly, as the community moved away from inline styles, the ability to target hover stated etc with them was never added. You NEED CSS if you want :hover. And if while we’re at it, media queries and pseudo elements would be nice too!

Vjeux actually agrees that inline styles are not suited for these cases.

But let’s look at the CSS in JS concept again, and the problems it solves.

Putting our CSS in Javascript and using inline styles solves all these problems. Why would we ever want to go back to the old ways?

But if we think about it, do we really need the styles to be inline? Of all the problems mentioned above, inline styles really only are required for Isolation. Or are they?

What if we could guarantee CSS selectors that would never leak? Managing class names is something that humans aren’t exactly great at. Most of us at least. But creating unique class names that target the elements exactly where we want is exactly what computers are great at. So why not let our CSS in JS actually create style tags, instead of using inline-styles?

I pulled down the React todomvc example and did just that. I built a quick and dirty CSS helper function that takes an object of styles and returns one or more classNames.

So now we went from this

<div className="my-heading">

to this:

<div style=styles.myHeading>

to now this:

<div className=css(styles.myHeading)>

I don’t think that’s bad at all. But one more thing has changed a little to actually support pseudo elements and hover states etc.

The styles object is no longer a simple object of styles. From this:

styles.myHeading = {
  color: 'black',
  fontSize: 36
};

We now have:

styles.myHeading = {
  node : {
    color: 'black',
    fontSize: 36
  }
};

Not a big change. But why? Well now you can put all sorts of other pseudo selectors right there on an element:

We now have:

styles.myHeading = {
  node : {
    color: 'black',
    fontSize: 36
  },
  'node:hover' : {
    color: 'red'
  },
  'node::before' : {
    content: '>'
  }
};

This is where the css function comes into play. It maintains a cache of all styles already in the stylesheets. But instead of doing the usual classname to styles mapping, it actually stored the reverse. It keeps track of all the styles already present on the page, and stores an id for each one.

function addStyleString(str) {
  var node = document.createElement('style')
  node.innerHTML = str
  document.body.appendChild(node)
}

function objToArray(obj){
  return Object.keys(obj).sort().map(function(key){
    return [key, obj[key]]
  })
}

function arrayToCSS(arr){
  return arr
  .map(function(style){
    return hyphenate(style[0]) + ':' + style[1] + ';'
  })
  .join('\n')
}

function hyphenate(string){
  return Array.prototype.reduce.call(string, function(str, letter){
    return letter.toLowerCase() === letter ? str + letter : str + '-' + letter.toLowerCase()
  }, '')
}

var cache = {}
var count = 0

function getIds(obj){

  return Object.keys(obj)
    .map(function(key){
      return getId(key, obj[key])
    })
    .join(' ')
}

function getId(key, obj){



  if(key.slice(0,4) !== 'node'){
    return ''
  }

  var temp = {}
  temp[key] = objToArray(obj)

  var json = JSON.stringify(temp)

  if(!cache[json]){
    cache[json] = 'style-' + count
    count = count + 1
    addStyleString('.' + cache[json] + key.slice(4) + ' {\n' + arrayToCSS(temp[key]) + '\n }')
  }

  return cache[json]
}

module.exports = getIds

When you pass it an object of this sort, it first divides it into individual selectors. Then it checks it’s cache for whether these styles already exist there or not. If not it, creates a new unique classname and adds the style to its cache.

it checks the initial key for the word node and then takes everything after that and puts it as an actual pseudo selector in the CSS file, and everything works great!

This is of course an early early prototype and a lot of tooling and smarts could be built into the system. I imagine the following as just the initial ideas:

I would love to hear what you think of this approach to CSS. Please let me know twitter : @naman34

Update: Just after posting this article I was point to projects that are trying to achieve something similar:

I think both projects have some good ideas. JSS, for example, has tackled the issue of redefining the same rule multiple times in JS using arrays.

However, JSS is pretty complicated to actually use. RCSS is very similar to how I wanted to solve the problem.

Instead of:

<div className=css(styles.myHeading)>

You now have this:

<div className=RCSS.registerClass(button).classname>

Don’t pay attention to the extra text, its almost the same workflow. The main difference is that you need to call this method somewhere:

RCSS.injectAll();

RCSS doesn’t automatically inject styles onto the page. You register styles and inject them manually. While I think this option can be useful, in general this is an extra step that no one wants to deal with.

I would think that an automatic inject on register is a good idea. Perhaps, with setImmediate/nextTick solution, to only inject once per event loop.

On the other hand, RCSS is still completely missing any solution for server-side rendering. JSS is better but not quite there either
UPDATE: Both RCSS and JSS have some ways to help with server-side rendering. Yet, both solutions leave a lot of room for improvement. With better tooling, things can be much better.

The next step here, in my opinion, is to bring some of the good ideas of JSS over to RCSS and then build good tooling around the system so that it works for all use cases with a good developer workflow. On the server and the client.

Update 2: After chatting with @oleg008 on the Gitter Channel for JSS, it turns out that JSS is essentially the same number of steps as RCSS. JSS has a special mixin for React that had me confused. Turns out that JSS has functionality that dynamically adds and removes styles from your page as react components are mounted and dismounted.

@oleg008 explained:

detaching 1 dom node per component will not impact the performance at all. if you application grows, selector engine needs to consider ALL selectors for EVERY element, this has a big impact on overall app perf
selector engine is fast, but if your styles become hundreds of kilobytes every node insert will feel this weight

This is not what I expected from browsers. I will try to set up some tests to find out more about the performance implications of removing styles from the page and finding out if it is faster than just leaving in extra styles on the page forever like the other solutions.

 
176
Kudos
 
176
Kudos

Now read this

Ideas on Improving Error Handling in Javascript

I like Javascript. I like it a lot. But of late, I’m really annoyed by the state of Error Handling in the language. There are some tools that improve the situation some of the times. Promises and Reactive Extensions both give you a... Continue →