kentcdodds / kentcdodds/css-in-js-precompiler

Discussion: the limits and tradeoffs for best-in-class precompilation

Open
#4 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
70
Forks
2
PR merge metrics
No merged PRs in 30d

Description

Kent, I'm trying to figure out a path forward for best-in-class precompilation *in combination* with dynamic props-based dynamic styles at render time. I.e. how to achieve a best-of-all worlds here.

Regarding this tweet:

https://twitter.com/kentcdodds/status/903767274749865984

why exactly would the following be a problem to precompile:

```js
const myStyles = {}
const MyDiv = glamorous.div(myStyles)

myStyles.color = 'red'

```

Is the idea that the reference to `myStyles` is used only at *render time* when `` is rendered?

If that's the case, couldn't that be solved by treating it immutably and making a clone of `myStyles` so that nothing more could be added to it?

That would enable precompilation to do whatever it wants to `myStyles` before passing it to `myStyles`.

Basically the sort of setups I imagine are javascript-based merging of objects to take the place of LESS/SASS/Stylus style mixins, inheritance etc. That would look like this:

```js
import { fooStyle, barStyle } from '../styles/global'
import { fontSize } from '../styles/variables'

let sizeIncrease = 2

const myStyles = {
...fooStyle,
...barStyle,
padding: 10,
fontSize: fontSize + sizeIncrease
}

const MyDiv = glamorous.div(myStyles, ({ primary }) => ({
backgroundColor: primary ? '#CC3A4B' : 'rgba(255, 255, 255, 0.5)',
}))
```

When it comes to building the babel plugin, perhaps the key thing in above is simply that `sizeIncrease` is not specified within the object literal. It's that sort of stuff we'd want to enable. On the extreme side, you could loop through an array like this:

```js
const sizes = [2, 5, 8]

const allStyles = sizes.map(size => ({
...fooStyle,
...barStyle,
padding: 10,
fontSize: fontSize + size
})
```

So the goal would be to support all such things in a precompile phase. It would be up to the developer to not access things on `window`, and that limitation would be acceptable. That's the sort of direction I'm seeing here.

What other sort of challenges do you see here? It seems discovering simply what parts of the code are intended to be pre-compiled are the primary aspect. You'd basically have to discover what arguments are passed to `glamorous.div` and back-track to figure out what portions of the code need to be pre-compiled (without executing things that possibly can't successfully evaluate in this environment).

To paint the picture of what sort of problems it seems we need to solve, perhaps looking at something more feasable would help. An idea would be something similar to `renderStatic` as you use for SSR:

```js
let { myStyles } = createStyles(() => {
const myStyles = {
...fooStyle,
...barStyle,
padding: 10,
fontSize: fontSize + sizeIncrease
}

const another = { color: 'purple' }

return { myStyles, another }
})
```

That's less than ideal, but at least it gets us there, as that's easy to target with a babel-plugin. Obviously the babel plugin would know to only preval what's within the function passed to `createStyles`. I think if we can take that as inspiration, we may be able to find a way to do it without the `createStyles` wrapper, while allowing for maximum flexibility without making too many tradeoffs.

----

I know the use case isn't important to you, but I think it is to a lot of people. You have a lot of experience with this, so any additional information you can think of to help this cause would be very valuable.

It's also more important now that you can render to a stream on the server. For example, now at the beginning of a request you can get all the stylesheets you will need just by knowing what chunks a given route depends on, and then append those stylesheets at the top of the page before even starting the streaming of what's rendered. Of course you need code-split stylesheets to do that, but let's just assume you do.

In that case I'm not sure what happens with prop-based dynamic style functions, which you'll still want, as after all the whole purpose is to have both static precompiled styles + dynamic styles. But I assume you have something cooking for the streaming use-case without precompilation.

In conclusion, I think what you've done with glamorous has paved the way for the ALL JS styling future that will serve us best. And if we can come up with some world-class solutions for preserving static stylesheets when the styles are in fact static *while of course gaining the full power of JS*, we'll have completed the picture here. The static stylesheets is especially important when it comes to code-splitting. If you have a setup that can generate multiple stylesheets for your dynamic imports, ultimately what you're gonna end up with is better cacheability, as the stylesheets will cache independently of both each other and the JS. And obviously that's just one of several benefits. There's a lot of incremental perf benefits by using static stylesheets where possible that in sum make a difference.

Contributor guide

Open the contributing guide

Research direction

Start with the proposed Babel plugin approach and the glamorous.div and createStyles examples in the issue; there are no files or tests named to anchor implementation. Define which static style expressions can be precompiled while preserving prop-based dynamic styles, then document concrete completion criteria and tests for those cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
babel, javascript
Domain
build-system, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.