Skip to content

Instantly share code, notes, and snippets.

@tomhodgins
Last active February 7, 2019 19:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomhodgins/beb79aa598b182e623ac27bf4b5e3f8d to your computer and use it in GitHub Desktop.
Save tomhodgins/beb79aa598b182e623ac27bf4b5e3f8d to your computer and use it in GitHub Desktop.

Types of Preprocessing

We want to end up with this as the result, but we want to:

  • use a variable while writing the stylesheet
  • output a width and height property with the variable's value in PX units
div {
  width: 50px;
  height: 50px;
}

Type 1: External to the language [good]

In this example, we are working 100% outside of the language we're preprocessing. All of our custom extensions here are written in JavaScript, and it's not aware of the content of the CSS it's working with - it only sees it as strings. It assembles and outputs CSS but at no point was parsing CSS needed. This is future-proof.

let size = 50
let width = `width: ${size}px;`
let height = `height: ${size}px;`
let selector = 'div'

outputRule(selector, [width, height])

Type 2: Internal to the language, invalid syntax [bad]

This is the route taken by preprocessors like Sass and Less and Stylus and PostCSS - it tries to extend the language from inside the language, but it does it in ways that are either invalid syntax in the original language, or valid syntax but doing it in ways that aren't future-proof (by camping on platform namespaces). One big flaw to this approach is that you have to parse the language, but you're constantly trying to reconcile two languages together and as they grow they may grow more incompatible with each other as either adds new features. This is not future-proof, you constantly have to worry about updating your parser and ultimately your tool gets less useful over time as the common ground between the two languages shrinks.

$size: 50;

div {
  width: ($size * 1px);
}

Type 3: Internal to the language, valid syntax [good]

The third way you can approach preprocessing a language is by including custom information inside the language you want to preprocess that is valid in that language, and doesn't camp on the platform namespace for future features. This is future-proof.

div {
  --size: 50;
  width: calc(var(--size) * 1px);
  height: calc(var(--size) * 1px);
}
div {
  --size: 50;
  width: var(--size);
  height: var(--size);
}

/*
  somewhere in JS we have:
  value => value + 'px'
*/

Bonus round, just so you don't think I'm immune to making the same mistake - element queries in all three types. Guess which one we have to worry about parsing ;)

Type 1: Element queries in valid JavaScript [good]

element(
  'div',
  {minWidth: 500},
  `[--self] { background: lime }`
)

Type 2: Element queries in invalid CSS [bad]

@element div and (min-width: 500px) {
  :self {
    background: lime;
  }
}

Type 3: Element queries in valid CSS [good]

@supports (--element("div", {"minWidth": 500})) {
  [--self] {
    background: lime;
  }
}

In these examples - the JS one we never have to worry about because it's valid JS. We can process and output the CSS we need from JS easily. The custom syntax we have to write a custom parser for, and to do that we have to now adopt the neverending task of making sure changes to CSS syntax don't break the assumptions we built into our own custom syntax at the time we designed it. The last example is valid CSS, but is guaranteed never to collide with any native CSS feature it will gain in the future. We also don't have to worry about parsing this since it's valid CSS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment