elm-css icon indicating copy to clipboard operation
elm-css copied to clipboard

Might Atomic CSS style output be better?

Open rtfeldman opened this issue 7 years ago • 4 comments

It's possible that Atomic CSS-style output (each declaration gets its own classname) would be better than doing hashed classes. It's not clear if this is a good idea, but it seems worth considering.

Design gotchas to watch out for: (some helpful replies to this tweet)

  1. What do we do in the case of custom properties generated with property? What do they compile to? How do we make sure that what they compile to does not suffer from https://github.com/rtfeldman/elm-css/issues/337?

  2. Consider #2 in http://fela.js.org/docs/introduction/Caveats.html - probably the solution is to (1) make border3 be a "mixin" that specifies borderWidth, borderColor, etc, (2) pre-resolve cascades, e.g. if I say css [ borderWidth (px 2), borderWidth (px 3), borderWidth (px 4) ] I get only one property in the output: border-width: 4px. Maybe we should do this anyway?

  3. In the "compile to a hash" world, people can't rely on CSS classes at all for e2e testing. Great! However, in this world, classnames are predictable enough that maybe people would.

  4. Attempting to memoize classname calculations using a Dict would almost certainly be a net loss from a perf perspective. Dict lookups take logarithmic time, whereas string concatenation is constant. So we shouldn't expect the same level of perf that Styletron's benchmarks show.

How to benchmark:

  1. Take a large page that's using the existing output, then recompile using the version with atomic style output. Compare load times for both. At the end of the day, this matters more than microbenchmarks - all things considered, did it make the page faster or slower?

  2. Try it with SSR as well. Wouldn't this increase downloaded HTML file size? Maybe it's fine with gzip though, if critical CSS is in

rtfeldman avatar Nov 27 '17 03:11 rtfeldman

  • When each declaration gets its own class, how would cascading work when there are multiple declarations for the same css poperty? Should all those declarations be collapsed into the one the gets priority? In that case, also collapse custom properties with the same name into one, and there should not be an issue concerning https://github.com/rtfeldman/elm-css/issues/337.
  • How do media queries cascade when generating 1 class per declaration? How is the order of the output determined? Styletron also seems to suffer from this. (https://github.com/rtsao/styletron/issues/113) Their solution is to not use overlapping mediaQueries and just use disjoint ones. It's a fix that is not inline with how I think people usually write media queries. But it is a fix nonetheless.
  • I think there are 2 options concerning this particular topic:
    • Don't care about the order of the output and fix it in userland.
    • Care about the order of the output and guarantee in some way that when defining overlapping media queries on the same element, they will be outputted in the order they are defined.

butterybread avatar Nov 30 '17 18:11 butterybread

When each declaration gets its own class, how would cascading work when there are multiple declarations for the same css poperty? Should all those declarations be collapsed into the one the gets priority?

The last one should win, I suppose unless an earlier one uses !important. So if I write css [ color blue, color green ] it should only emit the equivalent of color green, but if I write css [ color blue |> important, color green ], then it should only emit the equivalent of color blue.

https://github.com/rtfeldman/elm-css/pull/345 fixed #337 by changing to a "last one wins" policy regarding multiple usages of css inside a single attribute list, so that case can't cause problems anymore. 😅

rtfeldman avatar Nov 30 '17 20:11 rtfeldman

How do media queries cascade when generating 1 class per declaration? How is the order of the output determined? Styletron also seems to suffer from this. (rtsao/styletron#113) Their solution is to not use overlapping mediaQueries and just use disjoint ones. It's a fix that is not inline with how I think people usually write media queries. But it is a fix nonetheless.

Yeah, that seems pretty bad. I'm not sure if it's a deal-breaker, but it is very close to one in my mind. 🤔

rtfeldman avatar Nov 30 '17 20:11 rtfeldman

How do media queries cascade when generating 1 class per declaration? How is the order of the output determined? Styletron also seems to suffer from this. (rtsao/styletron#113)

@Stijnkool I thought about this some more, and I think we can make media queries continue to Just Work.

Suppose I write this:

button
    [ css
        [ backgroundColor (hex "f00")
        , color (hex "00d")
        , withMedia [ only screen [ Media.minWidth (px 1024) ] ]
            [ Css.maxWidth (px 300) ]
        , withMedia [ only screen [ Media.minWidth (px 768) ] ]
            [ Css.maxWidth (px 200) ]
        , withMedia [ only screen [ Media.minWidth (px 568) ] ]
            [ Css.maxWidth (px 120) ]
        ]
    ]
    [ text "click this!" ]

Ideally we'd want these things to be true:

  1. The media queries output in the same order, so they override each other in the expected way. This should be true even if someone later uses the same media queries, with the same definitions inside them, but in a different order. (This is how things work today.)
  2. As often as possible, we use one class per declaration in order to get the perf and download size benefits people are seeing with things like Styletron.

Here's something the above could compile to which satisfies both of these goals:

._bgc\(f00\) {
    background-color: #f00;
}

._c\(00d\) {
    color: 00d;
}

@media only screen and (min-width: 1024px) {
    ._m_o_s_minw1024px\(maxw300px\)m_o_s_minw768px\(maxw200px\)m_o_s_minw568px\(maxw120px\) {
        max-width: 300px;
    }
}

@media only screen and (min-width: 768px) {
    ._m_o_s_minw1024px\(maxw300px\)m_o_s_minw768px\(maxw200px\)m_o_s_minw568px\(maxw120px\) {
        max-width: 200px;
    }
}

@media only screen and (min-width: 568px) {
    ._m_o_s_minw1024px\(maxw300px\)m_o_s_minw768px\(maxw200px\)m_o_s_minw568px\(maxw120px\) {
        max-width: 120px;
    }
}
<button class="_bgc(f00) _c(00d) _m_o_s_minw1024px(maxw300px)m_o_s_minw768px(maxw200px)m_o_s_minw568px(maxw120px)">
    click this!
</button>

The key is that the media queries all get combined into one class, whereas everything else is one-declaration-per-class.

This way, we get to reuse things like common color and font declarations (in practice, those are highly unlikely to vary by media queries) but media queries still work as expected.

...or, supposing we murmur3 hashed them for smaller download sizes/faster CSS parsing:

._ab741d {
    background-color: #f00;
}

._d3ff3c {
    color: 00d;
}

@media only screen and (min-width: 1024px) {
    ._7cf09d {
        max-width: 300px;
    }
}

@media only screen and (min-width: 768px) {
    ._7cf09d {
        max-width: 200px;
    }
}

@media only screen and (min-width: 568px) {
    ._7cf09d {
        max-width: 120px;
    }
}
<button class="_ab741d _d3ff3c _7cf09d">
    click this!
</button>

(I'm not sure whether the mumur3 hashing is a net win in terms of performance. Will need to benchmark both approaches to learn more.)

Anyway, so it seems like that problem is also solvable!

rtfeldman avatar Dec 16 '17 15:12 rtfeldman