Wednesday, January 26, 2011

Jammit, Compass, and Safari: Can't we all just get along?

When Jammit is used with CSS files generated by Compass, the resulting CSS file may not work with Safari. This is because every Sass file that includes Compass will add the following line to the top of the generated CSS file:

    @charset "UTF-8";

That happens because something in Compass requires UTF-8, and Sass will generate that line if anything it is processing requires UTF-8. This is all fine...until you try to combine more than one of these standalone-css files with an asset packager such as Jammit.

Once Jammit combines all these CSS files, you end up with one CSS file that has multiple @charset directives in it, which according to the W3, is a no-no. Generating a CSS file with more than one @charset directive (and anywhere other than the very first line) is clearly against the W3's spec...but the only browser it seems to break is Safari.

But here's the thing: why do my CSS files need to have the @charset added by Sass when they don't actually get output with any non-ASCII characters?

In the end, my quick stop-gap solution was to remove them myself. I used to let Jammit do all the driving. During deployment it would force the regeneration of the CSS files and package them. Now I have to break out the Compass step and add my own sed command in order to remove all the @charset directives:



I think each one of these components has something to improve:

Safari:
If you don't support multiple @charset directives, I support you on that front. The spec says you're right. However...can't you just raise an error that says "ERROR: multiple @charset directives" or something? That would be much better than just randomly disabling half the CSS styles.

Sass:
Sass shouldn't add a @charset line if the generated CSS does not need it. If a mixin that uses UTF-8 is included but not used, and is not represented in the generated CSS, there is no need to add a @charset directive on its behalf.

Compass:
Compass is probably doing nothing wrong here. Though I do question why it needs to use UTF-8 characters. Maybe that's for a feature I haven't used yet.

Jammit:
In the end, I think Jammit is the real culprit here. It takes in N perfectly-valid CSS files and outputs one invalid CSS file. Jammit should remove duplicate @charset directives, and if they are conflicting, it should convert the charset of the conflicting stylesheet and into the charset being used by the output file.


3 comments:

Nathan said...

The reason Sass adds the @charset directive here is that you're importing a Compass module that has non-ASCII characters. This module declares @charset UTF-8 at the top of the file, since that's necessary for the file being to be parsed.

Since Sass is a full CSS3 superset, it must preserve this @charset declaration, even if it detects that the output stylesheet has no non-ASCII characters. In general, if you aren't using the module with the @charset declaration, you shouldn't import it and you should never see the @charset. However, if you @import "compass", that will transitively import all modules, landing you with the @charset even if you aren't using that module.

Incidentally, Compass uses a non-ASCII character in its font-face mixin.

scottwb said...

Thanks for that explanation, Nathan. The point about being a superset of CSS3 makes sense. I may take a look and see if I really need the font-face mixin. Good tip. (Thanks for chatting some of this out on IRC with me today too!)

scottwb said...

UPDATE: I've filed an issue ticket with jammit for this: https://github.com/documentcloud/jammit/issues#issue/120

I may take a stab at forking the project and contributing a patch there...