Skip to content
This repository

[request] seperate media/browser specific markup to seperate style sheet #241

Open
tomByrer opened this Issue January 05, 2012 · 40 comments
tomByrer

I have to agree with Sass's docs: @import... takes a separate (slow) HTTP request [per .css file]. But there are times when you want to work on a single .SCSS/.SASS source doc, tag some markup as browser-specific, and have the resultant .CSS contain a conditionally-include link to a second (also auto-generated from the single source) .CSS file.

Example scenario:
The main .SCSS file contains all the markup, with special notation for IE6 workarounds. Sass then splits off the IE6-tagged markup into "ie6-only.css", and inserts in main.css: <!--[if IE 6]> @import ie6-only.css /* no need to hard-code all that IE6 crud for 7% of the website visits */<![endif]-->

Inspiration:
http://perfectionkills.com/profiling-css-for-fun-and-profit-optimization-notes/
Search for SASS ;)

Idea on how to implement:
Not sure; I don't know Sass or Ruby well enough, perhaps something like the new Placeholder Selectors, or a pseudo-selector like ":-ie6external-any". I considered to define a custom Sass function to hack my own, but I keep gravitating that a tweak to the Placeholder Selectors can do the trick. I don't see other ways in the docs how to do de-interpolate into 2+ .CSS files.

cheers

tomByrer

related conversation: chriseppstein/compass#664

Nathan Weizenbaum
Owner

As I said on the Compass issue, I haven't seen enough evidence to indicate that explicit support for targeting multiple stylesheets is substantially better than using the existing @if control directive along with variables.

MattyBalaam

The solution I want is to be able to move all media queries for specific sizes into one place - preferably in the same file.

At the moment I can only compile code into something like this:

#Foo {  width: 21.2%;  }
@media screen and (max-width:600px) {
   #Foo { width: 100%;  }
}
@media screen and (min-width:1200px) {
  #Foo { width: 15.3%;  }
}
@media screen and (min-width:1600px) {
  #Foo { width: 9.4%;  }
}

.bar {  position: fixed;  width: 31.2%;   }
  @media screen and (max-width:600px) {
  .bar { position: relative; display: inline;  float: left;  width: 90%;  }
}
  @media screen and (min-width:1200px) {
  .bar { width: 75.3%;  }
}
  @media screen and (min-width:1600px) {
  .bar { width: 79.4%;  }
}

Which can really bloat code after multiple selectors are produced. It would be much better for me both for size and developing if it would read like this:

#Foo {  width: 21.2%;  }
.bar {  position: fixed;  width: 31.2%;  }

@media screen and (max-width:600px) {
  #Foo {  width: 100%;  }
  .bar {  position: relative; display: inline; float: left; width: 90%;
   }
 }
@media screen and (min-width:1200px) {
  #Foo { width: 15.3%;  }
  .bar { width: 75.3%; }
 }
@media screen and (min-width:1600px) {
  #Foo {  width: 9.4%;  }
  .bar {  width: 79.4%;  }
}

Edit: sorted out formatting (my first time putting code in comments)

Nathan Weizenbaum
Owner

@MattyBalaam It's not easy to safely re-order rules in a stylesheet. In your case I believe it works, but consider the following two chunks of CSS:

@media screen and (max-width:600px) {
  .foo {position: fixed}
}

.bar {position: relative}
@media screen and (max-width:600px) {
  .bar {display: inline}
}
.bar {position: relative}
@media screen and (max-width:600px) {
  .foo {position: fixed}
  .bar {display: inline}
}

Under the first, <div class="foo bar"></div> would have position: relative, while under the second it would have position: fixed. This is why Sass can't naively reorder rules. It's long been a desire of mine to have a way of figuring out ways to safely re-order in order to optimize the generated CSS, but this is a difficult problem that I haven't had time to focus on yet (and probably won't for a while).

tomByrer

I did post this request before I noticed the Compass thread; that is why I posted the link after. If that is a good workaround, then great! I do like the second example just above!

Perhaps then this request should not be regarded as an extra addition to SASS's codebase, but a request for such use be part of the "official" documentation?

MattyBalaam

@nex3 I see your point about some kind of intelligent re-ordering being a problem. But would there be any way of allowing this so the developer has control over where the rules are placed?

So throughout the file you would be able to say something like @target handheld {display: inline} and then at the end of the file you would be able to then call all the rules in a way like this:

@media screen and (max-width:600px) {
@include handheld
}

Obviously you may get times when rules conflict, but that would be no different from coding the CSS manually.

Nathan Weizenbaum
Owner

I think the correct way to handle that case will be post facto optimization. I don't want to add a feature that's just there so developers can manually optimize the size of their stylesheets.

MattyBalaam

So can I check you mean each CSS file after compilation would then need to be manually optimised? Or are you saying it could be done automatically, but some additional automated task outside sass would need to be created to do this.

Snugug

I was asked to provide a more specific usecase where this could be useful. As an aside, I really like the suggestion in the related Compass issue for an @target directive to specify what code goes where and thus not limiting this to just media queries. That being said, here's the more specific usecase: Enter SouthStreet, current one of the best-in-class toolset for Progressive Enhancement. Specifically, let's look at Enhance (Or Modernizr with yepnope support, which I prefer) and eCSSential. To understand why these tools are important, we need to remember a handful of things; first, not all media queries are width media queries, some media queries may actually change the way you want to lay out your site, loading non-essential CSS in a blocking manner is bad for front end performance, with the @target directive this will provide for more utility than just with media queries, and when it comes to RWD it's really just Progressive Enhancement in a very large scale and being able to separate out our media queries can go a long way in being future friendly.

So, with this in mind, let's take a look at Enhance/Modernizr. Both utilities allow for JavaScript based checking of browser features and then performing actions based on that including conditionally loading CSS in. If one were to take this approach, we may want a totally separate layout for devices that support touch. In an ideal environment, we'd be able to do something like the following (using Modernizr's syntax):

/* style.scss */
#foo {
  height: 20px;
  @target touch {
    height: 44px;
  }
}
/* style.css */
#foo {
  height: 20px;
}
/* style-touch.css */
#foo {
  height: 44px;
}
yepnope({
  test: Modernizr.touch,
  yep: 'css/style-touch.css'
});

All of our touch styling is now pushed out to a namespaced targeted CSS file that we can load in as we please.

If we're taking the eCSSentials approach, let's look at that same example, except this time we're going to combine it with the CSS Level 4 'pointer' media query:

/* style.scss */
#foo {
  height: 20px;
  @target coarse {
    height: 44px;
  }
}
/* style.css */
#foo {
  height: 20px;
}
/* style-touch.css */
#foo {
  height: 44px;
}
<link href="style.css">
<link href="style-touch.css" media="(pointer: coarse )">

This, when using the eCSSential style async loading, defers the loading of style-touch.css in browsers that don't support touch while those that do get it immediately (NOTE: eCSSential doesn't currently support MQs besides width ones, but the style of loading should be easily expandable to doing so).

Of course, there's also the third and final usecase that comes from Media Queries in general; specifically no media query fallbacks for browsers that don't support them, and especially for IE<9. Having the power to write IE hacks inline and have them print out in a separate stylesheet just for IE seems absolutely in line with Sass's ideals. Yes there are potential render issues, but these exist even when coding by hand, and if using the @target directive, the 'all' namespace could be reserved for printing to all stylesheets to help avoid this.

Could you do this all by hand with lots of creative partials and mixins? Sure you could, and that's what we're forced to do now, but having Sass handle this would be a huge time saver and make these great techniques readily available to those who aren't as skilled at the whole process that's required to do it by hand.

Also, I believe this may go without saying, but I'm also a fan of concating same media queries together under a single media query, although the same potential exists for render issues there. It's hard to get around this, and truthfully you'd have the same issues coding it by hand, so I'm not really sure if there's a convincing counter argument for that issue except to say make it a default-off compile time option with a warning about potential unexpected results in the readme.

Chris Eppstein
Collaborator

I really don't understand what objections to @target that @nex3 has other than it's possible to do this in other ways -- seeing as how all of Sass can be done in other ways.

Snugug

Like I said, I really like @target. It's an elegant solution to an otherwise very complex problem, and as long as we can interpolate the target, totally extensible by mixins and functions.

Nathan Weizenbaum
Owner
nex3 commented August 15, 2012

I can certainly see why you'd want to create multiple stylesheets for different browsers and different capability profiles. That's definitely a use case I'd like to support. What I don't understand is why @target is any better than @if for doing that. Your examples are still easily translatable into @if:

/* style.scss */
#foo {
  height: 20px;
  @if $target == touch {
    height: 44px;
  }
}

Compile the same stylesheet with different variables set and it works just like you're suggesting @target would work.

Snugug
Chris Eppstein
Collaborator

@nex3 it's not the same. The differences:

  1. additional s[ac]ss files must be created and configured
  2. The behavior of @target is to hide all the things not in the target. So the code would actually have to be:
/* style.scss */
#foo {
  @if not $target { // Yuck
    height: 20px;
  }
  @if $target == touch {
    height: 44px;
  }
}
  1. It seems likely that @target would be have better performance.
Scott Kellum

Having used something very similar to @if $target in production it is incredibly cumbersome. Isolating styles appropriately across multiple partials becomes annoying, especially when whole partials of styles might not be served to a particular environment. Without the ability to conditionally load partials every instance would need to have context explicitly stated.

In this project I had two environments I built separately, one for an app and one for the web. It did the trick and was a good solution but not without some headaches. @target seems like a very elegant solution to these issues.

Richard Lyon

I'm sorry if this confuses the issue, but what about something like an @as directive; This would mark either the current node or the children of the the @as to be compiled in a separate file.

@media all and (min-width: 600px) {
  @as $mobile-stylesheet;
  ...
}
.foo {
  @as $desktop-stylesheet;
  ...
}
@each $target in desktop, mobile {
    @as #{$target}.scss {
        @import style.css;
    }
}
Snugug

Thanks for chiming in Rich, but I think that actually is the opposite of what we're looking to do.

This is a language level change, so an end user shouldn't need to write an @each statement anywhere; the idea is to just wrap a piece of code and, at a language level, have it generate a separate stylesheet with just that wrapped code, so no need for the @each, and more importantly, we don't want to flat out @import all of style, we want separate stylesheets with desecrate code. If we take the @each out, then the @as directive as you've proposed doesn't seem to allow us to specify just what selectors from #foo or from @media we want to use; it seems to be all or nothing. I feel is may be worse from a user's perspective as the control for it is a bit clumsy; where does it start? Where does it end? Does it carry through partials? What if I want some code to go to all stylesheets, others to go to just one of the children sheets? I think a wrapped solution, either being @target or the @if is a better way to go, and feel that @target is the best of the three.

While I'm here though, a suggestion for either solution: the ability to specify that the output should be put in more than one spot, so something like this:

@target all, ie-9, ie-8, ie-7, ie-6 {}

or

@if $target == 'all' or $target == 'ie-9' or $target == 'ie-8' or $target == 'ie=7' or $target == 'ie-6' {}

Also, funny, now that I just wrote out those two, I would really hate to write out the @if for everything, really does hurt my mental model and could lead to some very easy to get wrong syntax errors expecting to be able to write a full @if statement. I now much prefer @target.

@nex3, with @if, what would the expected output of the following be? If we add onto @if, what happens here?

#foo {
  $target: 'print';
  @include invert-colors(blue);
}

#bar {
  $target: 'print';
  @include invert-colors(white);
}

#baz {
  $target: 'all';
  @include invert-colors(red);
}

@mixin invert-colors($start-color) {
  background: $start-color;
  color: invert($start-color);
  @if $target == 'print' and $start-color != 'white' {
    background: greyscale($start-color);
    color: invert(greyscale($start-color));
  }
  @else {
    color: invert($start-color);
  }
}

Does $target become the first reserved variable name in Sass? What happens if someone creates a $target variable? Will they get an error at all? If so, where? At variable creation or at use? Should it error at all? Maybe they just want to use the variable namespace $target without creating separate stylesheets? What happens if an existing mixin/function has $target as an argument? What will happen with all of those?

Can you chain target if statements with non-target if statements? If so, what happens when you do; does it only print if the entire if statement evals to true as per normal, or will it generate the the stylesheet than continue evaluating the if statement?

Essentially, the question is, what happens when you add new and unexpected functionality into preexisting mental models? Wouldn't it be better to create a new mental model for this truly new functionality?

Stan Angeloff

A bit further away from the @target discussion, I've been toying around with @buffers and I am also at the point where I need to implement media queries being outputted in separate files as well. For the purposes of my implementation, I was planning on treating each @buffer directive within an @imported file as an indicating that the file should also be saved to disk, e.g.:

// default.scss

#page {
  min-width: 960px;
  @buffer small-screen {
    min-width: 0;
  }
}

@import 'small-screen';
// small-screen.scss
@flush small-screen

I haven't gotten to do it yet, but it's on my list. If interested, subscribe to #116.

Nathan Weizenbaum
Owner
nex3 commented August 20, 2012

@Snugug I'm not particularly swayed by the idea that @if statements are harder to write or less semantic. You could always just write a mixin to make it more clear:

@mixin target($targets...) {
  @each $allowed-target in $targets {
    @if $target == $allowed-target {
      @content;
    }
  }
}

There's also no reserved variable names or other magic going on. There would just be a command-line argument to Sass that allowed you to set a global variable ($target) externally. It would function in every way like a global variable, including being assignable from local scopes.

@chriseppstein The differences you bring up are more compelling, although I'm not entirely sure why you'd want the separate target stylesheets not to have the contents from the primary stylesheet.

@scottkellum I don't understand what you mean by "isolating styles across partials." Can you give a more concrete example of how this was awkward?

Chris Eppstein
Collaborator

@nex3 because, in this case, you serve both stylesheets to the browser.

Nathan Weizenbaum
Owner
nex3 commented August 20, 2012

Okay, but why?

Chris Eppstein
Collaborator

@nex3 See yepnope.js, it's a very common strategy.

Nathan Weizenbaum
Owner
nex3 commented August 20, 2012

I can see why that would be useful for non-preprocessed stylesheets where it would be a pain to duplicate all the shared styles, but a preprocessor makes it easy to do so and just have one stylesheet per target. Why wouldn't you want that?

Snugug

For one, just about all of the optimization tools are designed to not have full stylesheets loaded in but only load in what's needed when, and to add on top instead of fully replacing. This is especially true for IE conditionals and low bandwidth where you specifically don't want to load in a replacing stylesheet (too large and cumbersome) and really truly only want the IE specific stuff.

Practical example time:

I'm building a responsive, mobile first website. I've done all of the progressive enhancement so that all of the design, coloring, images, etc… degrade nicely for IE8, but IE8 doesn't support media queries, so the "mobile" single column layout is shown instad of the "desktop" multicolumn layout. Instead of supplying IE8 and below with a complete stylesheet, I really only need to supply them with a supplemental layout stylesheet. This is, in fact, preferable as it is easier to see the difference between the two and because serving an entire new stylesheet would be a double download of all of the styles, meaning double the selectors (which IE hates), double the page blocking time, double everything. In a world where milliseconds means dollars, only loading in what's needed when it's needed is a preferable strategy to load it all and let God sort it out.

Chris Eppstein
Collaborator

@nex3 if there is only one dimension then the approach you've identified makes sense and is most optimal. But if there are two or more dimensions the approach falls apart as you have to permute all the dimensions in order to have a stylesheet to load that matches the exact client needs.

Nathan Weizenbaum
Owner
nex3 commented August 20, 2012

@Snugug My suggestion wasn't to load two stylesheets, one basic and another basic + IE8; rather, it was to only load the basic + IE8 stylesheet on IE8.

@chriseppstein I take your point. That would indeed be difficult to deal with in my suggested approach.

All right, I'm sufficiently convinced that @target is the best way to handle this use case.

Chris Eppstein
Collaborator

:) There is some discussion of a buffer/capture concept in another issue. We should sort out that use case and see if these two should be unified into a generalized feature.

Nathan Weizenbaum
Owner
nex3 commented August 20, 2012

From what I've seen, I dislike the semantics of @buffer. But that's something that should be discussed in its own issue.

Chris Eppstein
Collaborator

I'm not sold on it either, but if there's something there, they might be related.

Nathan Weizenbaum
Owner
nex3 commented August 20, 2012

That's why I suggested they open a pull request, so we can discuss it more thoroughly.

Phil Ricketts

I agree with @nex3's comments on #577, @buffer is useful in a single case, but @target as discussed is much more powerful.

Look forward to seeing progress on this.

Snugug
Snugug commented July 25, 2013

Any movement on this?

elyse holladay

Just chiming in that I really need/want this functionality as well! Would be SO helpful!

fuddl

it seems its currently not planned to be implemented?

Chris Eppstein
Collaborator

I've recently been toying with a different idea that is even more useful and powerful.

Kristian Dalgård

Would love to hear more about the progress on this. Something that is more useful and powerful?

Michael Otton

I have a specific need for this. I'm using sass to generate my css, part of the project I'm working on requires the project to be brand-able, i.e. separate css output just for the colours and nothing else. I was sure there was a way to do this by @extends but I was wrong. In the end I created a load of variables and had to separate out all the colour info out of 20+ scss _partials to do it. Problem is, this starts off small but in the end it is going to cause a huge headache the more brands get added. Is there no way at present to keep all the sass together logically and split out portions into another file(s)?

John Slegers

What about the following implementation? This is an implementation that's already possible in 3.2.

INPUT :

input/settings/normal.scss :

// Set module specific parameters
$module : 'normal';
$screen-min : '700';
$screen-max : '1199';

input/settings/widescreen.scss :

// Set module specific parameters
$module : 'widescreen';
$screen-min : '1200';
$screen-max : '1600';

input/core.scss :

@media only screen and (min-width: $screen-min) and (max-width: $screen-max) {
    div {
        @if $module == 'normal' {
            padding: 20px;
        }
        @if $module == 'widescreen' {
            padding: 30px;
        }
    }

    span {
        @if $module == 'normal' {
            padding: 60px;
        }
        @if $module == 'widescreen' {
            padding: 45px;
        }
    }   
}

input/normal.scss :

@import "input/settings/normal.scss";
@import "input/core.scss";

input/widescreen.scss :

@import "input/settings/widescreen.scss";
@import "input/core.scss";

input/global.scss :

@import "output/normal.css";
@import "output/widescreen.css";

OUTPUT :

output/normal.css :

@media only screen and (min-width: 700) and (max-width: 1199) {
  div {
    padding: 20px;
  }

  span {
    padding: 60px;
  }
}

output/widescreen.css :

@media only screen and (min-width: 1200) and (max-width: 1600) {
  div {
    padding: 30px;
  }

  span {
    padding: 45px;
  }
}

output/global.css :

@media only screen and (min-width: 700) and (max-width: 1199) {
  div {
    padding: 20px;
  }

  span {
    padding: 60px;
  }
}
@media only screen and (min-width: 1200) and (max-width: 1600) {
  div {
    padding: 30px;
  }

  span {
    padding: 45px;
  }
}
yareckon

I'd love to see a non hackish way to do this. jsledgers proposed workaround requires that you wrap everything in the file in an @if to get your separate files in multiple passes. Why not just make this easy and allow a module partial to be compiled to multiple files with @export or @target or whatever? That way you only have to wrap the things that need to be separated out in an a function/mixin rather than wrapping the whole file contents to hide/show everything.

Brad Frost

Hey everybody,
Adding a bump in here. Is there any movement on this? I think that this functionality is terribly needed. @Snugug, thanks for all the use cases; they're all things that I'm running into and have a real need to solve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.