38. Re-invent all the wheels!

May 10, 2019

I wrote this post for my newsletter, sign up here to get emails in your inbox.


We have a tendency of ignoring ideas we don’t understand. I ignored React because I didn’t think it was going to be useful for me.

Boy, was I wrong!

My introduction to CSS-in-JS was with styled-components and while I saw how I would use it, I had a lot of questions.

“Styles are generated during runtime? How can this be fast?”

“How does it even insert styles based on props?”

My favorite way of wrapping my head around such questions is to try to implement the thing myself. So, I built a mostly-useless css-in-js library.

The goal is not to build a custom solution because you don’t trust third party libraries, but to learn how a certain library or tool works so that you can appreciate what is going on under the hood and understand the trade-offs better.

I like to do this quite often. Most of these are throw-away experimentation code, but every once in a while it helps me notice something that was hidden in the Adjacent possible.

I was introduced to the concept of Adjacent Possible in Cal Newport’s book - So Good They Can’t Ignore You.

“We take the ideas we’ve inherited or that we’ve stumbled across, and we jigger them together into some new shape,” Steven Johnson explained. The next big ideas in any field are found right beyond the current cutting edge, in the adjacent space that contains the possible new combinations of existing ideas.

I was reminded about it recently in Swizec’s newsletter - What a hockey legend can teach you about career development (go read it).

So, in the spirit of re-inventing the library for learning purpose, I want to show how you can build your own CSS-in-JS. And we’ll call it use-css because hooks are so in right now. (Spoiler alert: it’s less than 25 lines)

Remember, the goal isn’t to create a bullet-proof library that handles all the use cases. It’s to understand the core concepts required.

Lets start from the end - how can someone use this library?

import css from 'use-css'

function Header(props) {
  const className = css(`
    font-size: 64px;
    font-style: italic;
    color: ${props.color};
  `)

  return <h1 className={className}>Big heading</h1>
}

There are a few things happening here:

  1. We want people to be able to write css that looks like css (not objects)

  2. We want these styles to be put in a <style> tag in <head> and give it a class name which can be put in the component here.

  3. We want to support dynamic styles based on props.

Cool? Cool.

Now let’s get into the library code.

From the API above, you can guess that we want to expose a function called css which returns a className. Let’s start there.

/* function that get's called */
function css(styles) {
  const className = doSomething(styles)
  return className
}

export default css

Now, let’s talk about what are the tasks we have to do inside that doSomething function:

  1. Create a new class name
  2. Insert styles in a <style> tag in <head> with this class name
  3. Return the class name?

There are 2 ways we can approach the first task. We can generate a random hash for each class:

function getNewClassName() {
  // prettier-ignore
  return Math.random().toString(36).substring(7)
}
// returns hj50d4, l2ldn6, 1wjh59

or we can keep a counter

let count = 0

function getNewClassName() {
  return 'c' + count++
}
// returns c0, c1, c2
It would be really cool if we can name the class based on the file and component name, example: .post--heading. But, we don’t have access to the filename during runtime, so this isn’t possible without a babel-plugin at build time.

I like the second approach more, so let’s run with that.

Alright, on to step 2: Inserting styles into a <style> tag inside <head>.

It would be nice to have a dedicated style tag where we can add our styles, so let’s create that first:

const container = document.createElement('style')
container.id = 'use-css'
document.head.append(container)
// creates <style id="use-css"></style> inside <head>

Perfect, the above code should only run once. Now on to inserting styles:

function insertStyles(styles) {
  // get new class name
  const className = getNewClassName()

  // grab our styles container by id
  const container = document.head.querySelector('#use-css')

  // add styles with the classname at the end
  container.append(`
    .${className} {
      ${styles}
    }
  `)

  // return this new className
  return className
}

This would insert the styles in head, and return the class name to be used by the React component.

That’s it! We have all the pieces that make it a functional CSS-in-JS library!

 

BONUS:

There are a few things that makes this library not very useful. Let’s try to tackle them:

Problem #1: This library would insert the same styles multiple times under different names - which would increase the size pretty fast.

We need some sort of pure hashing function - which returns the same class name every time for a set of styles.

I can think of one way to build this function. It’s not optimised for lookups but it does the job 🙃

const insertedStyles = []
/*
example of insertedStyles: [
  { name: 'c0', styles: 'font-size: 64px; font-style: italic;'},
  { name: 'c1', styles: 'color: blue;'}
]
*/

function getExistingClassName(styles) {
  // check if styles are already in the array
  const existingStyles = stylesArray.find(function(row) {
    return row.styles === styles
  })

  // if they are, return the corresponding name
  if (existingStyles) return existingStyles.name
  else return null
}

Through some digging around, it looks like most libraries (styled-components, emotion, glamor) use use a javascript implementation of MurmurHash2.

The code looks a little like this:

function doHash(str, seed) {
  var m = 0x5bd1e995
  var r = 24
  var h = seed ^ str.length

  while (length >= 4) {
    var k = UInt32(str, currentIndex)
    k ^= k >>> r
  }
  // 40 more lines
}

There are symbols here that I didn’t even know were valid javascript syntax, but it seems like the standard way to do it. You can use it as a block box dependency - @emotion/hash without looking at the source, we’ll leave this wheel for a different day ;)

Problem #2:

Because we insert styles directly into head, we can not support features like nesting, pseudo elements or media queries.

We need a thin layer of processing before we insert it. This is where stylis comes in - it’s a light weight (3Kb) css preprocessor that can run in the browser.

import stylis from 'stylis'

// input
// prettier-ignore
const styles = stylis('.container', `
  font-size: 21px;
  img {
    border-radius: 5px;
  }
  @media (max-width: 600px) {
    & { font-size: 16px; }
  }
`)

// output
const styles = `
  .container { font-size: 21px; }
  .container img { border-radius: 5px; }
  @media (max-width: 600px) {
    .container { font-size: 16px; }
  }
`

Problem #3: We can’t export these styles to a .css file

We built this library in a way that it inserts these styles when the component renders at runtime.

We can build a babel-plugin that extracts these styles and rewrites our file to insert the generated classname. If we are okay with losing the dynamic styles based on props, we can reduce the javascript size as well by completely removing the library from runtime.

That’s an exercise for a different newsletter though 😊

 

My intention here is not to draw your attention to these libraries but to appreciate the challenges that lie underneath and maybe find an Adjacent possible during the exercise.

I copied and published the above code to npm if you’d like to use it: npmjs.com/use-css and if you would like to play with the source, here’s a codesandbox link.

Hope that was helpful in your journey

Sid


Want articles like this in your inbox?
React, design systems and side projects. No spam, I promise!