We started out with sass files with something like this (obvious brevity is obvious):
// colors.scss
$green: #37ab2e;
$white: #FFFFFF;
// skin.scss
.bg_green { color: $green; }
.fg_white { color: $white; }
Then using webpack, we processed the sass and used css-loader in modules mode to use them in react components like this:
// btn.js
import c from 'classnames'
import s from 'cityons.m.scss'; // our tachyons-style lib
const Btn = () => (
<button className={c(s.bg_green, s.fg_white)}>
Green button with white text
</button>
);
We're experimenting with SSR, and the architecture we want to use processes files through babel for server-side usage, but crucially the server-side does not use a webpack bundle (although the client does).
So seeing as we're not using webpack loaders for server-side code (and the fact we've been wanting to kill sass for a long time), we decided to take the plunge into glamor.
We chose glamor because:
- It works out of the box in the browser and server (and has all the autoprefixer bells & whistles) without needing webpack
- It has a comprehensive & clear API
- It meant we could rewrite our CSS lib under the hood without actually having to change any usage of that lib (important)
In glamor's readme, the usage is like:
const rule = style({ color: 'green' })
// rule is an object
const Foo = () => <div {...rule}>Green!</div>
However, we rely heavily on classnames for composing styles inside the components, and the following doesn't work:
const green = style({ color: 'green' })
const bgwhite = style({ background: 'white' })
const Nope = () = <div className={classNames(green, bgwhite)}/>
This doesn't work because classNames accepts objects as arguments, so the output is useless to us.
Thankfully however, glamor has a neat trick — you can toString()
the rules you create, and it'll output a string class name that you can just use in place of what you were previously using css modules for.
So the example at the very top becomes the following:
// cityons/util.js
import R from 'ramda'
export const stringify = R.map(rule => rule.toString())
// R.map means we can map over an object, transforming the values and leaving the keys as-is
// colors.js
export default {
green: '#37ab2e',
white: '#FFFFFF',
}
// skin.js
import { style } from 'glamor'
import {stringify} from 'cityons/util'
import { green, white } from 'cityons/colors'
const rules = {
bg_green: style({ background: green }),
fg_white: style({ color: white }),
}
export default stringify(rules)
So the default export is a map of rule key to classname string. Which means the following code will work exactly like it did with sass & css-modules:
// btn.js
import c from 'classnames'
import s from 'cityons'
const Btn = () => (
<button className={c(s.bg_green, s.fg_white)}>
Green button with white text
</button>
);
Which meant I could swap out the whole underlying lib without any API change to users of the lib.