[feature request] More intelligent compression #116

Open
rupert654 opened this Issue Jun 12, 2011 · 110 comments

Comments

Projects
None yet

It would be great if SASS output could be configured to more intelligently compress output. Here are some examples:

# Combine name-spaced selectors
# SASS                             # Preferred Output                 # Current Output
body {                             body{border:3px solid #ccc;}       body{border-width:3px;border-color:#ccc;border-style:solid;}
  border: {
    width: 3px;
    color: #ccc;
    style: solid
    }
}

# Combine multiple declarations
# SASS                             # Preferred Output                 # Current Output
body { border: solid #ccc; }       body{border:3px solid #ccc;}       body{border:solid #ccc;}
body { border-width: 3px; }                                           body{border-width:3px;}

# SASS                             # Preferred Output                 # Current Output
body { color: #ccc; }              body{color:#ccc;cursor:pointer;}   body{color:#ccc;}
body { cursor: pointer; }                                             body{cursor:pointer;}          

# Ignore overridden declarations
# SASS                             # Preferred Output                 # Current Output
body { border: 2px solid #400; }   body{border:3px solid #ccc;}       body{border:2px solid #400;}
body { border: 3px solid #ccc; }                                      body{border:3px solid #ccc;}

This would allow you to more expressively organise stylesheets and partials without sacrificing on output length. For example, you could have color, typography and layout partials which, when compiled, gave the same output as if they had been written all mixed together.

There maybe gotchas with doing this due to the cascading nature of stylesheets and this effectively changes the ordering of declarations. However, I can't immediately think of specifics and the first case at least does not seem to be problematic.

This should also only apply when the compressed output option has been set as the readability of stylesheets may be impacted. For example, in development, it might be useful for the output to indicate that the designer has intended to split declarations in a particular way.

albeva commented Jun 13, 2011

I think this should be part of new option to enable / disable optimizations. Another opportunity for optimizations is coalescing common properties:

/* for example: */
.foo { width: 10px; height: 10px; color: red; }
.bar { width: 10px; height: 10px; color: yellow; }
/* would become: */
.foo, .bar { width:10px; height:10px; }
.foo { color: red; }
.bar { color: yellow }

This kind of optimization is not safe however and therefore users should be able to test and see what side effects if any appear.

This may not be 100% related to this thread but I've seen every other marginally related ticket being closed and referred here.

Specifically for dealing with media queries it would be useful to use some sort of output buffering to stash some css rules for later inclusion. The issue is hinted at in the media queries blog article where trying to correct for media queries inline will result in a number of identical @media declarations that have different rules in them. Output buffering will solve this really well. Each buffer would be named and additive so each time it is invoked, more is appended to the end of it. Once a buffer is output it is flushed.

Buffering is a completely different usecase than a @mixin. Buffers would allow for the creation of a responsive grid plugin that expected specific buffers for specific @media blocks. Additionally it'd be useful for projects that want to create print styles using @print blocks. Currently the only way to manage this is to simply add your additional rules directly to the media queries which is effective but can make the code a little harder to track. Not having buffers also completely removes the capability of using canned media queries baked into a compass extension, making it harder to convert projects like 320 and up into SASS for easy re-use.

Here's a simple example:

.left-column {
  width: 300px; // for browsers that don't support media queries
  @buffer small-devices {
    & { width: 100%; }
  }
  @buffer medium-devices {
    & { width: 150px; }
  }
  @buffer large-devices {
    & { width: 300px; }
  }
}
.main-column {
  width: 660px; // for browsers that don't support media queries
  @buffer small-devices {
    & { width: 100%; }
  }
  @buffer medium-devices {
    & { width: 330px; }
  }
  @buffer large-devices {
    & { width: 660px; }
  }
}


@media only screen and (min-width: 0) {
  @output small-devices;
}
@media only screen and (min-width: 768px) {
  @output medium-devices;
}
@media only screen and (min-width: 992px) {
  @output large-devices;
}
  • buffers are output at the time that @output is called in the natural CSS order. So if you add to a buffer after it's been output, nothing happens.
  • you can easily add more to a buffer using the names.
  • it might be useful to clear a named buffer with something like @clean my-buffer;
  • I don't know if it's possible to use the & symbol in content blocks like in my example but it'd be really useful.
  • It would also be useful to use the bubbling feature similar to how @media works. Something like div { @buffer my-buffer { width: 100px; } width: 200px; }

Didn't think about an output buffer approach for media queries but I gotta say it doesn't look bad.

The buffer solution really looks good here...

+1

behaba commented May 31, 2012

yeah +1 for the buffer !

  • 1 and + 1 for media query squishing!

+1 for everything suggested!

@ghost

ghost commented Jul 6, 2012

+1 for sure.

simmo commented Jul 24, 2012

+1 Great solution. Buffer would be a great feature.

+1 for not repeating "@media screen and" a thousand times in my live file

I've been hacking on a @buffer support in Sass and progress can be seen on StanAngeloff/sass@nex3:master...StanAngeloff:feature/buffers The code is working at the moment, but lacks some features (see further down).

I plan on using this for my own projects, but if there is enough interest and the code is bug-free, I would be happy to submit a pull request for inclusion in core Sass.

The syntax I used is:

$device-size:  small;
$device-width: 640px;

body {
  color: white;
  @buffer #{$device-size}-screen {
    color: black;
  }
}

h1 {
  color: white;
  @buffer #{$device-size}-screen {
    color: black;

    .no-js & { color: red; }
  }
}

@media only screen and (max-width: #{$device-width}) {
  @flush #{$device-size}-screen;
}

Several features can be seen in action in the snippet above:

  • Rule bubbling
  • Interpolations
  • Parent selector (&) support
  • Converting between .scss <=> .sass using sass-convert

There is support for using @buffer within .sass files as well:

$device-size:  small
$device-width: 640px

body
  color: white
  #{$device-size}-screen ->
    color: black

h1
  color: white
  #{$device-size}-screen ->
    color: black

    .no-js &
      color: red

@media only screen and (max-width: #{$device-width})
  <- #{$device-size}-screen

What doesn't work / is missing

  • Error reporting and debugging has not been tested extensively
  • Buffer functions are not provided, e.g., buffer-exists(..) or buffer-empty(..)
  • This one is more for personal use -- ability to write buffers to files, i.e., generating media-specific files so main stylesheet can remain optimised for desktop screens

Try it out

  • Clone the repo

    $ git clone git://github.com/StanAngeloff/sass.git
  • Check out the feature/buffers branch

    $ cd sass
    $ git checkout feature/buffers
  • Build and install the gem

    $ gem build *.gemspec
    $ gem install --local *.gem

Feedback welcomed very much.

simmo commented Jul 30, 2012

@StanAngeloff Very interested by this, it would be great if this was implemented into Sass.

Does anyone have any reasoning for why this wouldn't be possible?

battaglr commented Aug 7, 2012

+1. Media queries are fundamentals these days, so would be really handy a more efficient way to manage the output to group them. Could be the one suggested by @heygrady —great idea BTW—, or another one, but would be nice to see some changes in the current approach.

feross commented Aug 10, 2012

+1

Contributor

nex3 commented Aug 20, 2012

@StanAngeloff Please open a separate pull request for your @buffer stuff so we can discuss it further. It's not directly related to this feature request.

@nex3 I think that @heygrady should open the separated pull request, since was his original idea and he probably can explain more in deep the matter. It's a really good idea the @buffer stuff. BTW: great work with the hacking @StanAngeloff.

+1. Would love to see something like this.

@imbatta I didn't write any code, I was just offering an idea. I couldn't create a pull request without code. But thanks for thinking of me.

@heygrady Oh, you're right, I kinda suck in some GitHub stuff yet; sorry for the misunderstood. Hope the @buffer thing end up implemented.

But coming back to the original feature request, I don't think it's a good idea. These kind of compilation will cause many headaches for sure. Anyway, if many people is interested, it could be an optional way to compile.

vieron commented Sep 2, 2012

+1

+1

@StanAngeloff +1, how close is this to a pull request? This belongs in sass in my unqualified opinion.

@aaronjensen: I have actually used my fork for a while now (eat your own dog food-style) and I am not happy with buffers.

I am not 100% convinced they are the best way of tackling the issue with @-media queries. You can already use @-if statements to output different queries to different files for each breakpoint so you don't really need buffers for that. On the other hand, if you want everything in a single file, a simple script file can do that for you on each stylesheet update.

So there's not a lot more left for buffers to solve IMHO.

In terms of a pull request, there have been changes in Sass around variadic functions so a rebase would be difficult. Furthermore, tests are incomplete in my implementation and you can do silly things like flushing a buffer from within the buffer itself causing a recursion and eventually a stack overflow. A lot of the tests are already duplicates of existing ones so there must be a better way of doing things.

@StanAngeloff That's interesting to hear, I guess it makes sense. The only use case I could think of for it was media queries and combining them after the fact seems like it will work as long as you expect it and plan for it.

Thanks for the link to the script. Based of of that and a python version I found I just put together a sprockets processor that combines media queries: https://github.com/aaronjensen/sprockets-media_query_combiner

Maybe some will find it useful if all they want to do is merge media queries

I read through this thread, allthough pretty fast, so could have missed something, haven't read the source code either, so I'm just saying that these are my bare thoughts after trying out Thoughtbot:s fine bourbon-extension neat and being a bit irritated over the duplicated media queries produced.

However, I was thinking about a different solution than the ones discussed here — it would be to implement buffer/rendering in the compiler, by using hashes and tree structures to store(add/appending) the rules, and finally render them instead of the present instant rendering routine — that would result in clean non-duplicated media queries etc.

The only consideration that comes in mind is the rule order, keeping the order in each level (css-rules only comes in two levels — document level and media query level), that could be accomplished by adding a counter for each interpreted sass-rule, and using it as a sort-index value for the interpreted row (rule or comments etc.), so rendering would be in correct order, preserving order for rows (rules and comments) — even in media queries — roughly thinking...

The fine thing in this is that the programmer that is using sass doesn't have to think of advanced buffers and other things, the sass-compiler would just do it right all the time — resulting in happy sass using programmers... ;)

@jonascarlbaum I'm not entirely sure what you're describing, but I don't think there is a way to combine media queries and completely preserve the order of all rules if media queries are interleaved with non-media queries. Or am I missing something?

Did you see https://github.com/aaronjensen/sprockets-media_query_combiner ? It combines things without having to think about it. Of course it does put them all at the end of the css, so that changes the order.

subru77 commented Oct 10, 2012

+1

Thanks @aaronjensen, will check out
https://github.com/aaronjensen/sprockets-media_query_combiner - seems to
achieve what I want to... Sorry for being a bit unclear in my description,
media queries should always be at bottom, the rules with media queries
should appear in top-down order... I have no doubts about this will do it
the right way...

The only tiny thing I believe could go wrong is when combining media
queries this way is when they are defined with overlapping widths inside
other rules, as I remember that Less Framework 4 is defined, that uses
inheritance in the media queries (mobile landscape inherits the mobile
portrait rules). It would be hard to achieve an logical order of the media
queries, when using overlapping media queries inside other rules in sass.

But my thoughts about these combining overlapped width media query issues
is not a real life problem, it just addresses that some people might use
this inside-rule-media-query-syntax the wrong way ... I successfully used
sass and Less Framework 4 about a year ago, defining the media queries the
standard way in the bottom manually, and that is the sensible thing to do
in those cases...

Written on my iPhone

I would caution that, for Sass to remain successful, answers like, "another tool can do it", should be reviewed carefully. Sass has an issue with how to implement media queries and, more importantly, how to manage them on a significantly complex application.

What are the chances of getting the @buffer work turned into a Sass or Compass plugin? That way it can be installed on top of Sass without having to run a patched version of Sass. I think that is driving down adoption and preventing people from trying out what you've built.

I'm willing to hear that @buffer is more cumbersome in practice than it first appears. It may not make anything easier to work with. However, I worry about relying on 3rd party tools to cover up for the shortcomings of Sass. I would say that a tool like Media-query-combiner is more of a workaround than a long-term solution. I also feel like @bufffer has a broader application that may not even be understood.

The current official-ish solution is not ideal, as you end up with many duplicated media query rules. While a tool like sprockets-media-query-combiner may do the trick, it presupposes that users are on a project that has Sprockets available. Sass has a much broader audience than just Ruby on Rails developers and that means a Sprockets-based solution doesn't actually solve the problem for many users.

I personally see it as an issue that Sass is still so tied to Ruby on Rails. I see a lot of Node.js developers using Less.js instead of Sass because it integrates better with their platform. In my company, we use Sass on all of our projects and we rarely work on Ruby-based projects. We never have Sprockets available, many of our projects are on .NET. We use Compass to manage our real-time Sass compilation and simply forgo server-side, cached compilation that libraries like Sprockets afford.

I've seen threads debating the finer details of Sass vs Less vs Syltus syntax and I tend to agree that Sass is the most full featured and makes the most sense. However, portability is becoming an issue. Projects like libsass are trying to fix that (although I don't know how Compass would work with libsass). Recommending solutions to core issues that rely on platform specific 3rd party solutions is, IMHO, a poor approach.

Perhaps adding the essence of the sprockets-media-query-combiner to Sass is a better approach.

+1 for @media bubbling

Jakobud commented Oct 30, 2013

+1 for the @buffer idea.

nathggns commented Nov 8, 2013

Shouldn't this have been addressed by now?

👍

@ghost

ghost commented Jan 25, 2014

+1

rickalee commented Feb 7, 2014

Love the buffer concept as a workaround but compiler could be smarter. +1

Contributor

apfelbox commented Mar 20, 2014

A somehow related issue about better compression (or smarter @extend with placeholder selectors)

_inc.scss

%test { color: red; }

_imp#.scss (# = 1, 2, 3)

@import "inc";
.imp# { @extend %test; } // # = 1, 2, 3

main.scss

@import "imp1";
@import "imp2";
@import "imp3";

Which produces

.imp1, .imp2, .imp3 {
  color: red; }

.imp1, .imp2, .imp3 {
  color: red; }

.imp1, .imp2, .imp3 {
  color: red; }

I would have expected that placeholders are added only once, right before the place they are @extended for the first time.
I know, that technically the placeholder is duplicated, but I would have expected it to be unique in the project.

I know, that technically the placeholder is duplicated, but I would have expected it to be unique in the project.

different problem. You're looking for: https://github.com/chriseppstein/compass/tree/master/import-once

jaicab commented Apr 21, 2014

+1 for buffer, really nice and neat solution

+1 for buffer

Contributor

robwierzbowski commented May 15, 2014

CSSO solves a lot of these problems and has a Ruby wrapper. It might be worth adding a dependency to get most of this issue for free.

I like @lunelson's idea of applying @buffer to mixins: nex3#116 (comment).

It should group mixin @flush dumps by matching mixin arguments. For example:

@mixin breakpoint($width) {
    @media (min-width: $width) {
        @content;
    }
}
@buffer breakpoint(700px) {
    h1 {
        color: red;
    }
}
@buffer breakpoint(700px) {
    p {
        color: blue;
    }
}
@buffer breakpoint(900px) {
    p {
        color: purple;
    }
}
@flush breakpoint;

Should output:

@media (min-width: 700px) {
    h1 {
        color: red;
    }
    p {
        color: blue;
    }
}
@media (min-width: 900px) {
    p {
        color: purple;
    }
}

+1 for @append

@append is a much cleaner, and more broadly applicable solution to the problem @buffer tries to solve.

(LESS has additive mixin declarations, which can be a real pain, but also proves incredibly powerful at times.)

+1 to use the css-condense logic.

apipkin commented Dec 11, 2014

👍

Late to the party, but to me, an extension to Sass based on @stoyan's CSSShrink would be fantastic for intelligent compression. I do understand the challenges there though. Not trivial at all. http://cssshrink.com/velocity/

@tatemz tatemz referenced this issue in zurb/foundation-apps Feb 6, 2015

Closed

CSS Minification/Optimization #440

+1 for buffer

Jakobud commented Mar 10, 2015

For anyone who is using gulp, just use https://www.npmjs.com/package/gulp-combine-media-queries

It will combine your media queries together for you after compilation is complete. Works perfectly.

+1 for buffer

+1 for buffer. Being able to nest related CSS and then control where it gets output seems like a generally useful tool. Something like:

@buffer("label") {
  // SCSS code
}

@flush("label");

Then...

// Mobile First BABY!!
.box {
  margin: 20px 10px;

  @buffer("tablet") {
    margin-left: 20px;
    margin-right: 20px;
  }

  @buffer("desktop") {
    margin-top: 40px;
    margin-bottom: 40px;
  }
}

// elsewhere

@media (min-width: 600px) {
  @flush("tablet"); // Outputs .box overrides and anything else in "tablet" buffer
}

@media (min-width: 900px) {
  @flush("desktop"); // Outputs .box overrides and anything else in "desktop" buffer
}

+1 for buffer

robsonsobral commented Oct 26, 2015

Hi!

I saw this discussion is really old, but I think there's something new to be considered: HTTP/2.

Soon, the good practice concerning requests will be "it depends". On HTTP/2 is better to split the CSS by media query and to reduce the file size. So, maybe it's better to keep every @media on its own file and to import or not on a screen widt basis; or we could @flush content buffered on another file... I don't know. I'm new to SASS. What I know is that the situation is changing from when this discussion began.

Thanks!

matovas commented Jan 23, 2016

hi,
I wrote an article about the targeting of styles in Sass
http://www.matov.pro/blog/adaptivity-targeting-styles
it is realy now

Falven commented Apr 9, 2016

+1

dswwsd commented Jun 7, 2016

+1

dmitry commented Jun 7, 2016

Please use emoji, instead of +1. Use comments for something really helpful for the issue.

Herokid commented Jun 7, 2016

Ok, sorry for that.

El 7/6/16 a las 12:10, Dmitry Polushkin escribió:

Please use emoji, instead of +1.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
#116 (comment), or
mute the thread
https://github.com/notifications/unsubscribe/ABPv4mkrDCIZf3Tni8KwSE3hvHq0tLe9ks5qJUOjgaJpZM4AAU9t.

David Saenz / david@herokidstudio.es mailto:david@herokidstudio.es /
www.herokidstudio.es http://www.herokidstudio.es / 93 320 90 90 ·
Pamplona 89, 3 · 08018 Barcelona

_Herokid Studio_™

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