Skip to content
This repository

optimize IE-specific rules by placing them into an IE specific sheet? #664

Open
paulirish opened this Issue January 05, 2012 · 29 comments
Paul Irish

So this is a optimization that might be out of scope, but as compass does minification already, perf seems to be within scope to some degree..

from @kangax's article on css performance ....

In my case, there was close to a hundred of such .ie7 and .ie8 -based selectors in a final stylesheet. Some of them were universal. The fix was simple — move all IE-related styles to a separate stylesheet, included via conditional comments. As a result, there were that many less selectors to parse, match and apply.

Unfortunately, this kind of optimization comes with a price. I find that putting IE-related styles next to the original ones is actually a more maintainable solution. When changing/adding/removing something in the future, there’s only one place to change and so there’s less chance to forget IE-related fixes. Perhaps in the future tools like SASS could optimize declarations like these out of the main file and into conditionally-included ones.

Thoughts on how this could be done?

Kaelig

Very good idea.

However, this may be Sass' job to do this since it's the CSS authoring tool on top of which Compass was built.

Chris Eppstein

I've had this idea in a number of forms over the past 3 years. I always end up coming back to the fact that having a 1-1 correspondance between input files and output files is an incredibly simple approach that makes it easy for users to reason about how compilation works, etc.

But I see a world coming where sass will have to have vendor/feature specific output in order to take advantage of new CSS features, etc. So I think it's time to revisit this concept once again.

Off the cuff my idea for how to represent this in Sass would be to allow for arbitrary "targets" to be marked in sass:

@mixin first-child {
  @target not ie7 {
    &::first-child {
       @content;
    }
   }
  @target ie7 {
     &.first-child {
       @content;
     }
  }
}
.foo {
  @include first-child {
    color: black;
  }
}

and then by default sass would generate an output file for each detected target and a CLI option to specify the target could be added. To support new compilation target with special compilation features (like support for variables or calc() or any()) Sass could either have predefined targets or we could allow features to be mapped to targets via some configuration.

Looping in @nex3.

Kaelig

When thinking about it, we can already output separate stylesheets (one for IE6, one for IE7, one for IE8…) with constants and @if statements. Then call them with conditionnal comments.

Here is a quick example I just threw:

// _grid.scss
$ie6: false !default;
$ie7: false !default;

@if $ie6 {
  body {
    text-align: center;
  }
}

.column {
  float: left;
  @if $ie6 {
    display: inline;
  }
}
.clearfix {
  // clearfix rules…
  @if $ie7 {
    zoom: 1;
  }
}

// ie6.scss
$ie6: true;
@import 'grid';

// ie7.scss
$ie7: true;
@import 'grid';

// application.scss
@import 'grid';

Output:

// ie6.css
body {
  text-align: center;
}

.column {
  float: left;
  display: inline;
}


// ie7.css
.column {
  float: left;
}

.clearfix {
  zoom: 1;
}


// application.css
.column {
  float: left;
}

It's as easy as .ie7 &-like selectors to maintain, and serves smaller stylesheets to modern browsers.

What do you think about this technique?

Mario Ricalde

I like your idea Chris, this is actually how I was picturing this in my mind. Keep the code inline and just define to what file you want to output the rules within the block. This would be a major feature that could help us developers work with Vendor features.

Also @target makes a lot of sense.

Nathan Weizenbaum
Collaborator

I'm with @Kaelig on this one: it seems like @target-style functionality is already possible with @if today. Maybe all that's missing is a way to set variables to true via the command line?

Mario Ricalde

@nex3 no, it's not possible. @Kaelig is missing the point completely.

What you can do right now with @if conditionals is just traverse and define via variables if it should be output in the same file.

What @target does is that it's going the code inside the block will be output to a target file ( not in the same file ).

Nathan Weizenbaum
Collaborator

@kuroir If you generate your CSS multiple times with different options specified, though, you'll get different output files just like target (as in @Kaelig's example).

Chris Eppstein

If statements are enough if the compiler behavior is not customizable on a per-target basis, but if the compiler is going to have compilation modes to support new syntax improvements in CSS then I think it makes sense to expose that concept to the stylesheets an leave the mapping of syntax modes and stylesheet tweaks to frameworks like compass.

If the goal is to emit a core stylesheet and a per-target auxiliary stylesheet then if statements are not enough.

Chris Eppstein

Also these huge if/else if blocks scream for a case statement at the very least.

Nathan Weizenbaum
Collaborator

The correspondence between compiling to calc() and friends and specific browser targets is basically independent of whether we use @target or @if; either way, Compass can manage the correspondence, as you say.

I'm not sure why you'd want a core shared stylesheet and multiple more-specific stylesheets, as opposed to just having the more-specific stylesheets contain everything a given browser needs.

Chris Eppstein
Nathan Weizenbaum
Collaborator

Why can't compass just spin up multiple Sass compilers for the primary entry point, each with different initial variables set and different output locations?

Chris Eppstein
MattyBalaam

Off topic, but I've wondered before about the possibility of Sass automatically moving all @media rules so the screen sizes aren't declared over and over again.

lecodeur

Been using the approach Kaelig mentions for some time now and am really pleased about it.

Can even push it a bit with an $ie version and target "lower than" versions of explorer:

// in _myinclude.scss
$ie: 9 !default;
selector {
  @if $ie < 9 {
    border: solid 2px #000;
  }
  @else { // for ie > 9, webkits, ff etc
    // fancy CSS3 rules, shadows, radius etc
  }
}

// in ie8.scss
$ie=8
@import "myinclude"

// in ie7.scss
$ie=7
@import "myinclude"
Chris Eppstein

The question here is not whether if statements can be made to work. The question is whether a dedicated feature provides a compelling enough user experience over if-statements to warrant it.

Matt Garrett

@chriseppstein Your first response's sample code looks like it would be a pleasure to use. I'd loathe having to riddle my sass with if statements.

With this feature, Compass's mixins could be updated to put vendor prefixed properties into their own browser targets. Developers would just need to tell Compass to create multiple css files for each target, maybe through some new configuration option. Their interfaces wouldn't deliver useless styles, and it would be a relatively easy upgrade.

Chris Eppstein

I should point out that Compass is already set up for this using if statements. Example

Nathan Weizenbaum
Collaborator

If the primary difference between @target not ie7 and @if not $ie7 is aesthetic, as seems to be the case, I don't believe it's worth adding a new feature.

Scott Davis
Collaborator

I'm not totally sure the argument is aesthetic only we are talking about 2 things here extending the compiler to have multiple output files for a given stylesheet and a new directive to tell the compiler which file to render the conditional css too. The major difference between @if and @target is that currently using if @if you have to spin up multiple compilers and handel setting variables for each which would straight be a pain I think ultimately this is a convenience feature and the time it takes to implement @target will pay for its-self almost instantly since one will not have to spend hours copy and pasting selectors to target IE specific bugs all I would need it to define the @target and everything is done. While refactoring to use @if is possible its just not practical to ask someone that may not have a firm understanding of sass / computer languages (think designers) to utilize the @if method using @target and processing it for them lowers the bar for entry for this feature and I think that there is something to be said for ease of use.

Nathan Weizenbaum
Collaborator

@scottdavis There's no need to spin up multiple instances of the executable. All you need to do is have multiple entry-point files, each of which defines the variables for its particular compilation profile (e.g. screen-ie7.scss would define $ie7: true and then @import "screen").

I don't see why it would be more difficult to tell designers to use @if $ie7 { ... } than to use @target ie7 { ... }.

Scott Davis
Collaborator

@nex3 my understanding is that @target will only render out that contents of that block to the intended target. Now i fully do understand the argument you have using @if but its going to create a huge headache in large projects where im going to have to maintain an abundance of if nested if statements. The goal is to keep the stylesheets as dry as possible and to achieve what im proposing below you would need to wrap everything in @if's to get the results to match.

Targets would need to be configured using sass options but i think the way im envisioning it has a cleaner use case then smelling up my stylesheets with @if statements all over the place. Being able to target specific areas that need adjusted in ie or print etc would save me tons of time hunting down the selectors and pasting them into the ie file when i start to work on ie bugs. Having this work the same way as @media but actually write it out to the other file.

How i envisioned target working

.foo {
  @include clearfix;
  .bar {
    background-color:red;
    float:left;
    @target ie {
      background-color:blue;
    }
  }
}

// COMPILED

//screen
.foo {
  overflow:hidden;
  zoom:1;
}
.foo .bar {
  background-color:red;
  float:left;
}

//ie
.foo .bar {
  background-color: blue;
}

now using if's

.foo {
  @include clearfix;
  .bar {
    background-color:red;
    float:left;
    @if $ie {
      background-color:blue;
    }
  }
}

// COMPILED

//screen
.foo {
  overflow:hidden;
  zoom:1;
}
.foo .bar {
  background-color:red;
  float:left;
}

//ie
.foo .bar {
  background-color:red;
  float:left;
  background-color: blue;
}

We should avoid the unnecessary repetition in the output

Nathan Weizenbaum
Collaborator

Why can't the IE stylesheet include the same selectors as the normal stylesheet? Why not serve only screen.css to normal browsers and only screen-ie.css to IE? This will decrease both the number of HTTP requests IE needs to do and (slightly) the byte size of the output file.

Chris Eppstein

in theory, rendering full stylesheets or auxillary stylesheets could both be supported by @target, but I agree that having full stylesheets that are served by browser detection is better in most cases.

As I've said before this feature boils down to two things:

  1. The use of @target can be auto detected and the framework can own the duplicative renders according to a convention instead of placing the responsibility on the user.
  2. @target can integrate with internal engine configuration to target compilation modes that take advantage of new structural features in CSS3+ (like moz-any, calc, etc)

In my mind, the Sass engine should have an option for enabling new features like:

:targets => {
  :moz => {:enable => {:any => :moz, :calc => :moz}},
  :opera => {:enable => {:any => true, :calc => :o}}
}

The engine would have an argument for render that indicates the target. Frameworks would then own the job of mapping the serving of stylesheets to particular feature sets and output modes.

Chris Eppstein

Note: this is all predicated on the notion that there is some efficiency in having an Engine reused across multiple renders for different targets. If this turns out to be untrue, then there's probably no point in having @target.

Nathan Weizenbaum
Collaborator

The use of @target can be auto detected and the framework can own the duplicative renders according to a convention instead of placing the responsibility on the user.

The framework can also set up a convention for multiple renders using @if with variables. You can even auto-detect it if you really want by searching the stylesheets for uses of the appropriate variables.

@target can integrate with internal engine configuration to target compilation modes that take advantage of new structural features in CSS3+ (like moz-any, calc, etc)

This would require a much tighter binding of target names and specific browsers/versions than I'd want to put in core Sass. Even if we went with @target, I wouldn't want Sass proper to have any opinion about which targets should be used and what they meant. Mapping those is a job for Compass.

In my mind, the Sass engine should have an option for enabling new features like:

This is doable with @if.

Note: this is all predicated on the notion that there is some efficiency in having an Engine reused across multiple renders for different targets. If this turns out to be untrue, then there's probably no point in having @target.

Since we keep a global in-memory cache of parse trees, there wouldn't be a difference between re-using one engine or using multiples.

Chris Eppstein

You keep saying things can be done. I keep saying that's not the point and has never been disputed by me. The point is whether it's the best way for things to be done. Grepping for @if statements and analyzing their logic is clearly more dangerous than a dedicated feature. Not all stylesheets compiled by compass have to @import compass -- a dedicated feature would be unambiguous and based in project configuration instead of loose convention.

I don't see how @if can enable different compilation output for things like combinatorial expansion of nested selectors using any() instead of commas.

Regarding the in-memory parse tree storage, it's not global by default, but it can be done by passing the cache_store option to the engine by compass -- we don't currently do that. Still it seems like these would only vary at the CSSize step, so all the results of both check_nesting passes and the perform step could be shared -- this would be some win compared to a new engine for each.

cimmanon
Collaborator

@evanwills

Now that IE9 & 10 support CSS3 media queries it's much harder to stop IE downloading both the IE9 (or IE10) specific versions of the CSS and the non-IE version of the CSS

The proposed @target is no different than an @if statement, because it would be a Sass language construct, not a CSS one. Conditional Comments are for feeding specific styles to specific versions of IE, not media queries.

damtur

I think @target is a good Idea as I we are using separate css files for IEs which then are added to other css. IE.css is a bundled version of all other css rules packed into one files but only for IE.
+1 for @target but it has to provide option build ONLY parts with that target.

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.