contivero / hasmin

Hasmin - A Haskell CSS Minifier
BSD 3-Clause "New" or "Revised" License
57 stars 4 forks source link

Grouping of media queries #2

Closed logankaser closed 7 years ago

logankaser commented 7 years ago

Hello again, its been some time.

So, grouping media queries, why would that be useful?

Here is an example (Limited to two per breakpoint) from a site I built using my tool, Cell. As you can see these could be easily combined. Now you might be thinking, surely only generated CSS would have such waste when it comes to media queries, however anyone using a modular CSS component library or organizing their code by class likely has such repetition. This is mostly due to the common use of a limited set of breakpoints that are used as the cutoffs for all media queries in a codebase. For example, one designer may use Large, Medium and Small sizes (each with rem or pixel values) while another may prefer more granularity. Whatever the case, the practice of using a limited set of breakpoints is widespread.

@media all and (min-width: 24rem) {
  .Fz\(s2\)\@xs {font-size: 1.2rem;}
}
@media all and (min-width: 24rem) {
  .Px\(s04\)\@xs {padding-left: .25rem; padding-right: .25rem;}
}
@media all and (min-width: 32rem) {
  .D\(ib\)\@sm {display: inline-block;}
}
@media all and (min-width: 32rem) {
  .Fz\(s1\)\@sm {font-size: 1rem;}
}
@media all and (min-width: 48rem) {
  .D\(b\)\@md {display: block;}
}
@media all and (min-width: 48rem) {
  .D\(ib\)\@md {display: inline-block;}
}
@media all and (min-width: 64rem) {
  .Pos\(s\)\@lg {position: static;}
}
@media all and (min-width: 64rem) {
  .Px\(s1\)\@lg {padding-left: 1rem; padding-right: 1rem;}
}
@media all and (min-width: 80rem) {
  .D\(b\)\@xl {display: block;}
}
@media all and (min-width: 80rem) {
  .D\(n\)\@xl {display: none;}
}

So technique is to combine media queries that use the same condition. For example:

@media all and (min-width: 24rem) {
  .Fz\(s2\)\@xs {font-size: 1.2rem;}
  .Px\(s04\)\@xs {padding-left: .25rem; padding-right: .25rem;}
}
@media all and (min-width: 32rem) {
  .D\(ib\)\@sm {display: inline-block;}
  .Fz\(s1\)\@sm {font-size: 1rem;}
}
@media all and (min-width: 48rem) {
  .D\(b\)\@md {display: block;}
  .D\(ib\)\@md {display: inline-block;}
}
@media all and (min-width: 64rem) {
  .Pos\(s\)\@lg {position: static;}
  .Px\(s1\)\@lg {padding-left: 1rem; padding-right: 1rem;}
}
@media all and (min-width: 80rem) {
  .D\(b\)\@xl {display: block;}
  .D\(n\)\@xl {display: none;}
}

I never noticed your request for an issue and since you so promptly libraryfied your code I feel I owe this to you now that I have noticed, Cheers!

contivero commented 7 years ago

Thanks for taking the time! It's been a while since I last looked into CSS stuff, but IIRC one of the issues combining normal rules is the possibility of changing behavior by modifying the cascade. In this example, you are combining only adjacent rules, which is always safe. Do you know if media queries have some kind of precedence according to their location in the file? In other words, would changing this:

@media all and (min-width: 24rem) {
.Fz\(s2\)\@xs {font-size: 1.2rem;}
}
@media all and (min-width: 32rem) {
.D\(ib\)\@sm {display: inline-block;}
}
@media all and (min-width: 24rem) {
.Px\(s04\)\@xs {padding-left: .25rem; padding-right: .25rem;}
}

where there is an @media rule between the two 24rem ones, into this:

@media all and (min-width: 24rem) {
.Fz\(s2\)\@xs {font-size: 1.2rem;}
.Px\(s04\)\@xs {padding-left: .25rem; padding-right: .25rem;}
}
@media all and (min-width: 32rem) {
.D\(ib\)\@sm {display: inline-block;}
}

be always safe? In this particular example, I believe it is, but I'm not sure if it's always so.

I can look into having adjacent rules merged, but I don't think I'll be getting into more aggressive mergings any time soon. Merging is quite misleading, because you tend to see big gains in the minified size, but after gzip compression it's either negligible, or it might even be bigger than if no merging had been done.

Anyway, I'll see what I can do.

contivero commented 7 years ago

I implemented a basic merging of adjacent @media rules, as in the example you posted. You can find it in version 0.3.3 (commit 945b3c) Try it out whenever you have some time and let me know what you think.

I have been reading some of the other minifier's issues (e.g. ben-eb/cssnano#111, jakubpawlowicz/clean-css#687), and as I imagined, merging rules in general isn't trivial, but the current approach of merging only adjacent rules should be safe. I hope it's enough for your needs.

logankaser commented 7 years ago

This is great!

Raw:        6811b
NoMerge:    5798b
Merge:      5497b
Gzip:       1696b
Gzip+Merge: 1637b

Not everyone will see the gains that I have, but it definitely does what I wanted it too. Thanks!

logankaser commented 7 years ago

Also the hlinting made your code so much more readable, thanks for that.